Computerhaus Quickborn

8 Wege zur JavaScript-Exzellenz​

Wer die Grundlagen von modernem JavaScript aus dem Effeff beherrscht, erzielt schneller bessere Ergebnisse.Gorodenkoff | shutterstock.com JavaScript ist eine unglaublich robuste, vielseitige und leistungsfähige Sprache, die oft schon standardmäßig alles bietet, was Entwickler brauchen. Der Schlüssel zum Erfolg liegt jedoch darin, das gesamte Spektrum der Programmiersprache zu kennen – und zu wissen, welche Aspekte, beziehungsweise Konzepte die eigene Applikation bereichern können.   In diesem Artikel lesen Sie, wie Sie als Developer das Maximum aus den heute in JavaScript verfügbaren Tools und Bibliotheken herausholen. 1. Variablendeklarationen verwenden Variablen sind so alt wie das Programmieren selbst – und stellen auch in modernem JavaScript noch immer ein Schlüsselkonzept dar. In diesem Zusammenhang sollten wir uns zunächst vergegenwärtigen, dass bei JavaScript const den Vorzug vor let erhält – und uns fragen, warum das so ist. Mit const wird eine Konstante deklariert, also eine Variable, die sich nicht verändert. Entsprechend nutzen wir const wann immer es möglich ist, denn seine Unveränderlichkeit (Immutability) macht es weniger komplex. Sie müssen sich bei einer Konstante keine Gedanken darüber machen, wie sie sich verhält oder sich im Programmverlauf verändern könnte. So ermöglicht const, einen Wert zu speichern und überall bedenkenlos zu verwenden. Das Konzept der Immutability ist ein tiefgreifendes, das sich ganz allgemein im Software Design widerspiegelt – insbesondere aber in der funktionalen und reaktiven Programmierung: Hier dient es dazu, die Gesamtstruktur größerer Systeme zu vereinfachen. Ein weiterer wichtiger Aspekt von const ist seine Funktionsweise im Fall von Objekten und Sammlungen. In diesen Fällen verhindert es, dass die Referenz auf die Variable geändert wird (allerdings nicht, dass deren interner Zustand verändert wird). Das offenbart Wichtiges über die interne Struktur von JavaScript: Unter der Haube sind Objekt- und Sammlungsvariablen Pointer – sie belegen also Speicherplatz. Wenn Sie sich dazu entscheiden, const zu verwenden, können Sie das nicht ändern. Natürlich gibt es auch Fälle, in denen wir eine Variable benötigen, die wirklich eine Variable ist. In diesem Fall kommt let zum Zug – obwohl JavaScript auch über das Keyword var verfügt. Den Unterschied zwischen let und var zu kennen, kann Ihnen dabei helfen, Variable Scoping zu durchdringen, was wiederum für fortgeschrittenere Konzepte (dazu gleich mehr) von Bedeutung ist. Wird let deklariert, beschränkt das die Variable auf den Block, in dem sie deklariert ist, während var sie in den übergeordneten Geltungsbereich „hochzieht“. Dadurch ist var besser sichtbar, aber auch fehleranfälliger. Es empfiehlt sich deshalb mit Blick auf Ihren Code, var durch let zu ersetzen. 2. Collections und Operators verstehen Funktionale Operatoren zählen zu den leistungsstärksten Funktionen von modernem JavaScript: Mit map, flatMap, reduce oder forEach wird es möglich, Repetitions über Collections mit einer übersichtlichen, selbsterklärenden Syntax auszuführen. Diese funktionalen Programmierkonstrukte machen Code besser, respektive direkt lesbar. Wenn Sie ein Programm schreiben, versuchen Sie in der Regel, eine bestimmte Business-Funktion umzusetzen – beispielsweise Rückmeldung von einer API zu erhalten und diese auf Grundlage von Benutzereingaben zu verarbeiten. Für diesen Task benötigen Sie einen Loop. Dieser ist aber nur ein notwendiger Teil der Logik und sollte nicht zu viel Platz im Programm einnehmen. Funktionale Operatoren lassen sich nutzen, um den Loop zu beschreiben, ohne die übergeordnete Bedeutung zu verschleiern. Ein Beispiel: const albums = [ { artist: "Keith Jarrett", album: "The Köln Concert", genre: "Jazz" }, { artist: "J.S. Bach", album: "Brandenburg Concertos", genre: "Classical" }, { artist: "The Beatles", album: "Abbey Road", genre: "Rock" }, { artist: "Beastie Boys", album: "Ill Communication", genre: "Hip Hop"}]; genreInput = "rock"; console.log( albums.filter(album => album.genre.toLowerCase() === genreInput.toLowerCase()) ) Allgemein soll der obenstehende Code bewirken, eine Liste von Musikalben nach Genre zu filtern. Die integrierte Methode filter für das Array albums gibt eine neue Sammlung zurück, auf die die übergebene Funktion angewendet wurde. Die Looping-Logik wurde auf das Wesentliche reduziert, um den Sinn des Codes besser hervorzuheben. An dieser Stelle ist jedoch anzumerken, dass traditionelle Loops nach wie vor eine wichtige Rolle spielen. Insbesondere, wenn diese sehr komplex ausfallen und mehrere Iteratoren beinhalten oder es um enorm große Loop Bodies geht. 3. Promises nutzen Asynchron zu programmieren ist von Natur aus schwierig, weil dabei per Definition mehrere Aktionen gleichzeitig ablaufen. Das hat zur Folge, sich mit der sich mit der Verflechtung von Events befassen zu müssen. Glücklicherweise verfügt JavaScript über starke Abstraktionen für diese Konzepte. Promises sind die erste Verteidigungslinie, wenn es darum geht, asynchrone Komplexitäten zu managen – und die Keywords async/await bieten eine weitere, zusätzliche Ebene, die es ermöglicht, asynchrone Operationen in einer synchron aussehenden Syntax zu schreiben. Als Softwareentwickler nutzen Sie Promises oder asynchrone Funktionen in Bibliotheken. Die allgegenwärtige fetch-Funktion, die in den Browser (und auch in serverseitige Plattformen wie Node) integriert ist, ist dafür ein gutes Beispiel: async function getStarWarsPerson(personId) { const response = await fetch(`https://swapi.dev/api/people/${personId}/`); if (response.ok) { // ... } Die von uns definierte Funktion enthält async, während die verwendete Funktion (fetch) mit await modifiziert wird. Das sieht aus wie eine normale Zeile mit synchronem Code, ermöglicht jedoch, dass fetch zu einem anderen Zeitpunkt ausgeführt wird – gefolgt von was auch immer als Nächstes kommt. Diese Sequenz „befreit“ den Event Loop, der sich um andere Dinge kümmern kann, während fetch läuft.   Promises sind nicht allzu schwer zu verstehen, aber sie führen Sie tiefer in die Semantik tatsächlicher, asynchroner Operationen ein. Die Idee: Ein Promise-Objekt repräsentiert eine asynchrone Operation, seine resolve– und reject-Methoden das Ergebnis. Der Client-Code verarbeitet die Resultate schließlich mit den Callback-Methoden then() und catch(). Zu beachten ist dabei, dass JavaScript nicht wirklich „concurrent“ ist: Es verwendet asynchrone Konstrukte, um Parallelität zu unterstützen, aber es gibt nur einen Event Loop, der einen einzelnen Betriebssystem-Thread repräsentiert. 4. Shortcuts kennen Die leistungsstarken Shortcuts von JavaScript können die Developer Experience entscheidend optimieren. Die folgenden fünf Operatoren reduzieren einige der gängigsten und umständlichsten Aspekte der Programmierarbeit mit JavaScript auf einige wenige Tastenanschläge.   Spread Mit dem Spread-Operator können Sie auf einzelne Elemente eines Arrays oder Objekts verweisen: const originalArray = [1, 2, 3]; const copiedArray = [...originalArray]; copiedArray.push('foo'); // [1,2,3,’foo’] Das funktioniert auch für Objekte: const person = { name: "Alice", age: 30 }; const address = { city: "New York", country: "USA" }; const fullInfo = { ...person, ...address }; Destruct Destructing bietet Ihnen eine prägnante Möglichkeit, die Elemente eines Arrays oder Objekts in ihre Bestandteile zu „zerlegen“: const colors = ["red", "green", "blue"]; const [firstColor] = colors; firstColor === “red”; const person = { name: "Alice", age: 30, city: "London" }; const { city } = person; city === “London”; Diese elegante Syntax kommt besonders häufig zum Einsatz, um Module zu importieren: const express = require('express'); const { json, urlencoded } = require('express'); Destructing unterstützt darüber hinaus auch benannte Parameter und Standardwerte. Optional Chaining Die alte Praxis manueller Null-Checks wird mit Optional Chaining durch einen einzigen, übersichtlichen Operator ersetzt: const street = user?.profile?.address?.street; Ist einer der Roots und Branches in dieser Dot-Access-Chain null, wird das gesamte Konstrukt zu Null aufgelöst (anstatt eine Nullpointer Exception auszulösen). Logical Assignment Logische Zuweisungen gibt es in den Nullish-Varianten and, or sowie strict. Die letztgenannte Variante: let myString = null; myString ??= “Foo”; myString ??= “Bar”; myString === “Foo”; Hierbei ist zu beachten, dass sich myString nur ändert, wenn es tatsächlich null (oder undefiniert) ist. Nullish Coalescence Mit Nullish Coalescence können Sie ganz einfach zwischen einer Variablen, die möglicherweise null ist, und einem Standardwert wählen: let productName = null; let displayName = productName ?? "Unknown Product"; productName === “Unknown Product”; 5. Scopes und Closures nicht fürchten Mit Blick auf die JavaScript-Grundlagen sind Scopes und Closures bedeutende Konzepte. Der Begriff „Scope“ ist davon abgesehen in allen Programmiersprachen zentral. Er bezieht sich einfach auf den Sichtbarkeitsbereich einer Variablen. Es geht also um die Frage(n): Wo kann eine Variable genutzt werden, sobald sie deklariert wurde – und wo ist sie sichtbar? Closure bezeichnet hingegen die Art und Weise, wie der Geltungsbereich einer Variablen unter besonderen Umständen funktioniert. Wird ein neuer Funktions-Scope deklariert, werden die Variablen im umgebenden Kontext für diesen verfügbar gemacht. Im Grunde also ein simples Konzept, dessen Namensgebug darauf beruht, dass der umgebende Scope den inneren „umschließt“. Closures haben weitreichende Auswirkungen: Sie können sie verwenden, um Variablen zu definieren, die für den größeren Kontext von Bedeutung sind – und dann Funktionsblöcke definieren, die auf diese Variablen angewendet werden (wodurch Ihre Logik stark eingeschränkt oder gekapselt wird). Das Konzept in Pseudocode: outer context variable x function context do stuff with x x now reflects changes The same idea in JS: function outerFunction() { let x = 10; function innerFunction() { x = 20; } innerFunction(); console.log(x); // Outputs 20 } outerFunction(); In diesem Beispiel ist innerFunction() eine Closure: Sie greift auf die Variable über ihren übergeordneten Bereich zu (auch als lexikalischer Scope bezeichnet). Die Closure hat also Zugriff auf die Variablen des Scops, indem sie deklariert wird – nicht in dem, in dem sie gecallt wird. Wie bereits erwähnt, ist Unveränderlichkeit ein Grundsatz der funktionalen Programmierung. Für ein sauberes Design ist es also zu vermeiden, Variablen zu ändern. In unserem Beispiel verstößt die Änderung von x gegen diese Richtlinie. Der Zugriff auf diese Variable ist jedoch eine wesentliche Funktion. Diese Art und Weise, Closures einzusetzen, ist bei funktionalen Collection-Operatoren wie map und reduce noch wichtiger. Sie bieten eine saubere Syntax, um Aufgaben auszuführen und haben außerdem Zugriff auf den lexikalischen Scope, in dem sie deklariert sind. 6. Fehler behandeln Error Handling wird uns als Programmierer wohl für den Rest unserer Tage begleiten. Zum Glück ist die Fehlerbehandlung bei modernem JavaScript inzwischen dank zahlreicher Optimierungen relativ ausgereift. Es gibt zwei grundlegende Arten von Fehlern: normale, synchrone Codefehler und asynchrone Ereignisfehler. Fehlerobjekte enthalten Fehlermeldungen und Ursachenobjekte sowie Stacktraces, also Auszüge aus dem Call Stack zum Zeitpunkt des Fehlers. Der Hauptmechanismus für normale Fehler ist der gute alte try-catch-finally-Block und sein Ursprung, das Schlüsselwort throw. Im Fall von asynchronen Fehlern wird es etwas kniffliger: Zwar ist die Syntax mit catch-Callbacks und reject-Aufrufen für Promises sowie Catch-Blöcken für asynchrone Funktionen nicht allzu komplex. Diese asynchronen Aufrufe gilt es jedoch im Auge zu behalten und sicherzustellen, dass sämtliche Fehler behandelt werden. 7. Programmierstil richtig wählen Kluge JavaScript-Entwickler sind offen für verschiedene Programmierparadigmen. Schließlich unterstützt JavaScript sowohl objektorientierte als auch funktionale, imperative und reaktive Programmierung. Es gibt keinen (vernünftigen) Grund, diese Möglichkeiten nicht zu nutzen. Sie können Ihr Programm entweder um einen dieser Stile herum aufbauen oder (je nach Use Case) auch mehrere verschiedene miteinander vermengen. Das ist typisch für JavaScript: Es gibt mehr als einen Weg zum Ziel. 8. KI-Unterstützung abwägen KI-Programmierassistenten gibt es noch nicht allzu lange – und doch sind sie als Unterstützungsleistung für viele Entwickler fast nicht mehr wegzudenken. Allerdings gilt das auch für moderne IDEs. Und auch wenn man denken könnte, dass kein Dev ohne VS Code, IntelliJ oder Eclipse auskommt, sieht die Realität mancherorts anders aus. Es gibt nämlich auch Entwickler, die die Posix-Befehlszeile zusammen mit Vim oder Emacs so einsetzen, dass eine visuelle IDE mit Maus im Vergleich dazu eher klobig wirkt. Ebenso wird es weiterhin einige Profis geben, die ohne KI effektiver programmieren. Das liegt allerdings im Wesentlichen daran, dass sie die Grundlagen perfekt beherrschen und selbst in komplexen Systemen direkt erkennen, wo Optimierungspotenzial besteht. Viele andere werden KI-basierte Coding-Tools in einem ähnlichen Ausmaß nutzen wie IDEs – und damit (hoffentlich) ihr Grundlagenwissen (nicht nur wenn es um JavaScript geht) weiter ausbauen. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox! 

8 Wege zur JavaScript-Exzellenz​ Wer die Grundlagen von modernem JavaScript aus dem Effeff beherrscht, erzielt schneller bessere Ergebnisse.Gorodenkoff | shutterstock.com JavaScript ist eine unglaublich robuste, vielseitige und leistungsfähige Sprache, die oft schon standardmäßig alles bietet, was Entwickler brauchen. Der Schlüssel zum Erfolg liegt jedoch darin, das gesamte Spektrum der Programmiersprache zu kennen – und zu wissen, welche Aspekte, beziehungsweise Konzepte die eigene Applikation bereichern können.   In diesem Artikel lesen Sie, wie Sie als Developer das Maximum aus den heute in JavaScript verfügbaren Tools und Bibliotheken herausholen. 1. Variablendeklarationen verwenden Variablen sind so alt wie das Programmieren selbst – und stellen auch in modernem JavaScript noch immer ein Schlüsselkonzept dar. In diesem Zusammenhang sollten wir uns zunächst vergegenwärtigen, dass bei JavaScript const den Vorzug vor let erhält – und uns fragen, warum das so ist. Mit const wird eine Konstante deklariert, also eine Variable, die sich nicht verändert. Entsprechend nutzen wir const wann immer es möglich ist, denn seine Unveränderlichkeit (Immutability) macht es weniger komplex. Sie müssen sich bei einer Konstante keine Gedanken darüber machen, wie sie sich verhält oder sich im Programmverlauf verändern könnte. So ermöglicht const, einen Wert zu speichern und überall bedenkenlos zu verwenden. Das Konzept der Immutability ist ein tiefgreifendes, das sich ganz allgemein im Software Design widerspiegelt – insbesondere aber in der funktionalen und reaktiven Programmierung: Hier dient es dazu, die Gesamtstruktur größerer Systeme zu vereinfachen. Ein weiterer wichtiger Aspekt von const ist seine Funktionsweise im Fall von Objekten und Sammlungen. In diesen Fällen verhindert es, dass die Referenz auf die Variable geändert wird (allerdings nicht, dass deren interner Zustand verändert wird). Das offenbart Wichtiges über die interne Struktur von JavaScript: Unter der Haube sind Objekt- und Sammlungsvariablen Pointer – sie belegen also Speicherplatz. Wenn Sie sich dazu entscheiden, const zu verwenden, können Sie das nicht ändern. Natürlich gibt es auch Fälle, in denen wir eine Variable benötigen, die wirklich eine Variable ist. In diesem Fall kommt let zum Zug – obwohl JavaScript auch über das Keyword var verfügt. Den Unterschied zwischen let und var zu kennen, kann Ihnen dabei helfen, Variable Scoping zu durchdringen, was wiederum für fortgeschrittenere Konzepte (dazu gleich mehr) von Bedeutung ist. Wird let deklariert, beschränkt das die Variable auf den Block, in dem sie deklariert ist, während var sie in den übergeordneten Geltungsbereich „hochzieht“. Dadurch ist var besser sichtbar, aber auch fehleranfälliger. Es empfiehlt sich deshalb mit Blick auf Ihren Code, var durch let zu ersetzen. 2. Collections und Operators verstehen Funktionale Operatoren zählen zu den leistungsstärksten Funktionen von modernem JavaScript: Mit map, flatMap, reduce oder forEach wird es möglich, Repetitions über Collections mit einer übersichtlichen, selbsterklärenden Syntax auszuführen. Diese funktionalen Programmierkonstrukte machen Code besser, respektive direkt lesbar. Wenn Sie ein Programm schreiben, versuchen Sie in der Regel, eine bestimmte Business-Funktion umzusetzen – beispielsweise Rückmeldung von einer API zu erhalten und diese auf Grundlage von Benutzereingaben zu verarbeiten. Für diesen Task benötigen Sie einen Loop. Dieser ist aber nur ein notwendiger Teil der Logik und sollte nicht zu viel Platz im Programm einnehmen. Funktionale Operatoren lassen sich nutzen, um den Loop zu beschreiben, ohne die übergeordnete Bedeutung zu verschleiern. Ein Beispiel: const albums = [ { artist: "Keith Jarrett", album: "The Köln Concert", genre: "Jazz" }, { artist: "J.S. Bach", album: "Brandenburg Concertos", genre: "Classical" }, { artist: "The Beatles", album: "Abbey Road", genre: "Rock" }, { artist: "Beastie Boys", album: "Ill Communication", genre: "Hip Hop"}]; genreInput = "rock"; console.log( albums.filter(album => album.genre.toLowerCase() === genreInput.toLowerCase()) ) Allgemein soll der obenstehende Code bewirken, eine Liste von Musikalben nach Genre zu filtern. Die integrierte Methode filter für das Array albums gibt eine neue Sammlung zurück, auf die die übergebene Funktion angewendet wurde. Die Looping-Logik wurde auf das Wesentliche reduziert, um den Sinn des Codes besser hervorzuheben. An dieser Stelle ist jedoch anzumerken, dass traditionelle Loops nach wie vor eine wichtige Rolle spielen. Insbesondere, wenn diese sehr komplex ausfallen und mehrere Iteratoren beinhalten oder es um enorm große Loop Bodies geht. 3. Promises nutzen Asynchron zu programmieren ist von Natur aus schwierig, weil dabei per Definition mehrere Aktionen gleichzeitig ablaufen. Das hat zur Folge, sich mit der sich mit der Verflechtung von Events befassen zu müssen. Glücklicherweise verfügt JavaScript über starke Abstraktionen für diese Konzepte. Promises sind die erste Verteidigungslinie, wenn es darum geht, asynchrone Komplexitäten zu managen – und die Keywords async/await bieten eine weitere, zusätzliche Ebene, die es ermöglicht, asynchrone Operationen in einer synchron aussehenden Syntax zu schreiben. Als Softwareentwickler nutzen Sie Promises oder asynchrone Funktionen in Bibliotheken. Die allgegenwärtige fetch-Funktion, die in den Browser (und auch in serverseitige Plattformen wie Node) integriert ist, ist dafür ein gutes Beispiel: async function getStarWarsPerson(personId) { const response = await fetch(`https://swapi.dev/api/people/${personId}/`); if (response.ok) { // ... } Die von uns definierte Funktion enthält async, während die verwendete Funktion (fetch) mit await modifiziert wird. Das sieht aus wie eine normale Zeile mit synchronem Code, ermöglicht jedoch, dass fetch zu einem anderen Zeitpunkt ausgeführt wird – gefolgt von was auch immer als Nächstes kommt. Diese Sequenz „befreit“ den Event Loop, der sich um andere Dinge kümmern kann, während fetch läuft.   Promises sind nicht allzu schwer zu verstehen, aber sie führen Sie tiefer in die Semantik tatsächlicher, asynchroner Operationen ein. Die Idee: Ein Promise-Objekt repräsentiert eine asynchrone Operation, seine resolve– und reject-Methoden das Ergebnis. Der Client-Code verarbeitet die Resultate schließlich mit den Callback-Methoden then() und catch(). Zu beachten ist dabei, dass JavaScript nicht wirklich „concurrent“ ist: Es verwendet asynchrone Konstrukte, um Parallelität zu unterstützen, aber es gibt nur einen Event Loop, der einen einzelnen Betriebssystem-Thread repräsentiert. 4. Shortcuts kennen Die leistungsstarken Shortcuts von JavaScript können die Developer Experience entscheidend optimieren. Die folgenden fünf Operatoren reduzieren einige der gängigsten und umständlichsten Aspekte der Programmierarbeit mit JavaScript auf einige wenige Tastenanschläge.   Spread Mit dem Spread-Operator können Sie auf einzelne Elemente eines Arrays oder Objekts verweisen: const originalArray = [1, 2, 3]; const copiedArray = [...originalArray]; copiedArray.push('foo'); // [1,2,3,’foo’] Das funktioniert auch für Objekte: const person = { name: "Alice", age: 30 }; const address = { city: "New York", country: "USA" }; const fullInfo = { ...person, ...address }; Destruct Destructing bietet Ihnen eine prägnante Möglichkeit, die Elemente eines Arrays oder Objekts in ihre Bestandteile zu „zerlegen“: const colors = ["red", "green", "blue"]; const [firstColor] = colors; firstColor === “red”; const person = { name: "Alice", age: 30, city: "London" }; const { city } = person; city === “London”; Diese elegante Syntax kommt besonders häufig zum Einsatz, um Module zu importieren: const express = require('express'); const { json, urlencoded } = require('express'); Destructing unterstützt darüber hinaus auch benannte Parameter und Standardwerte. Optional Chaining Die alte Praxis manueller Null-Checks wird mit Optional Chaining durch einen einzigen, übersichtlichen Operator ersetzt: const street = user?.profile?.address?.street; Ist einer der Roots und Branches in dieser Dot-Access-Chain null, wird das gesamte Konstrukt zu Null aufgelöst (anstatt eine Nullpointer Exception auszulösen). Logical Assignment Logische Zuweisungen gibt es in den Nullish-Varianten and, or sowie strict. Die letztgenannte Variante: let myString = null; myString ??= “Foo”; myString ??= “Bar”; myString === “Foo”; Hierbei ist zu beachten, dass sich myString nur ändert, wenn es tatsächlich null (oder undefiniert) ist. Nullish Coalescence Mit Nullish Coalescence können Sie ganz einfach zwischen einer Variablen, die möglicherweise null ist, und einem Standardwert wählen: let productName = null; let displayName = productName ?? "Unknown Product"; productName === “Unknown Product”; 5. Scopes und Closures nicht fürchten Mit Blick auf die JavaScript-Grundlagen sind Scopes und Closures bedeutende Konzepte. Der Begriff „Scope“ ist davon abgesehen in allen Programmiersprachen zentral. Er bezieht sich einfach auf den Sichtbarkeitsbereich einer Variablen. Es geht also um die Frage(n): Wo kann eine Variable genutzt werden, sobald sie deklariert wurde – und wo ist sie sichtbar? Closure bezeichnet hingegen die Art und Weise, wie der Geltungsbereich einer Variablen unter besonderen Umständen funktioniert. Wird ein neuer Funktions-Scope deklariert, werden die Variablen im umgebenden Kontext für diesen verfügbar gemacht. Im Grunde also ein simples Konzept, dessen Namensgebug darauf beruht, dass der umgebende Scope den inneren „umschließt“. Closures haben weitreichende Auswirkungen: Sie können sie verwenden, um Variablen zu definieren, die für den größeren Kontext von Bedeutung sind – und dann Funktionsblöcke definieren, die auf diese Variablen angewendet werden (wodurch Ihre Logik stark eingeschränkt oder gekapselt wird). Das Konzept in Pseudocode: outer context variable x function context do stuff with x x now reflects changes The same idea in JS: function outerFunction() { let x = 10; function innerFunction() { x = 20; } innerFunction(); console.log(x); // Outputs 20 } outerFunction(); In diesem Beispiel ist innerFunction() eine Closure: Sie greift auf die Variable über ihren übergeordneten Bereich zu (auch als lexikalischer Scope bezeichnet). Die Closure hat also Zugriff auf die Variablen des Scops, indem sie deklariert wird – nicht in dem, in dem sie gecallt wird. Wie bereits erwähnt, ist Unveränderlichkeit ein Grundsatz der funktionalen Programmierung. Für ein sauberes Design ist es also zu vermeiden, Variablen zu ändern. In unserem Beispiel verstößt die Änderung von x gegen diese Richtlinie. Der Zugriff auf diese Variable ist jedoch eine wesentliche Funktion. Diese Art und Weise, Closures einzusetzen, ist bei funktionalen Collection-Operatoren wie map und reduce noch wichtiger. Sie bieten eine saubere Syntax, um Aufgaben auszuführen und haben außerdem Zugriff auf den lexikalischen Scope, in dem sie deklariert sind. 6. Fehler behandeln Error Handling wird uns als Programmierer wohl für den Rest unserer Tage begleiten. Zum Glück ist die Fehlerbehandlung bei modernem JavaScript inzwischen dank zahlreicher Optimierungen relativ ausgereift. Es gibt zwei grundlegende Arten von Fehlern: normale, synchrone Codefehler und asynchrone Ereignisfehler. Fehlerobjekte enthalten Fehlermeldungen und Ursachenobjekte sowie Stacktraces, also Auszüge aus dem Call Stack zum Zeitpunkt des Fehlers. Der Hauptmechanismus für normale Fehler ist der gute alte try-catch-finally-Block und sein Ursprung, das Schlüsselwort throw. Im Fall von asynchronen Fehlern wird es etwas kniffliger: Zwar ist die Syntax mit catch-Callbacks und reject-Aufrufen für Promises sowie Catch-Blöcken für asynchrone Funktionen nicht allzu komplex. Diese asynchronen Aufrufe gilt es jedoch im Auge zu behalten und sicherzustellen, dass sämtliche Fehler behandelt werden. 7. Programmierstil richtig wählen Kluge JavaScript-Entwickler sind offen für verschiedene Programmierparadigmen. Schließlich unterstützt JavaScript sowohl objektorientierte als auch funktionale, imperative und reaktive Programmierung. Es gibt keinen (vernünftigen) Grund, diese Möglichkeiten nicht zu nutzen. Sie können Ihr Programm entweder um einen dieser Stile herum aufbauen oder (je nach Use Case) auch mehrere verschiedene miteinander vermengen. Das ist typisch für JavaScript: Es gibt mehr als einen Weg zum Ziel. 8. KI-Unterstützung abwägen KI-Programmierassistenten gibt es noch nicht allzu lange – und doch sind sie als Unterstützungsleistung für viele Entwickler fast nicht mehr wegzudenken. Allerdings gilt das auch für moderne IDEs. Und auch wenn man denken könnte, dass kein Dev ohne VS Code, IntelliJ oder Eclipse auskommt, sieht die Realität mancherorts anders aus. Es gibt nämlich auch Entwickler, die die Posix-Befehlszeile zusammen mit Vim oder Emacs so einsetzen, dass eine visuelle IDE mit Maus im Vergleich dazu eher klobig wirkt. Ebenso wird es weiterhin einige Profis geben, die ohne KI effektiver programmieren. Das liegt allerdings im Wesentlichen daran, dass sie die Grundlagen perfekt beherrschen und selbst in komplexen Systemen direkt erkennen, wo Optimierungspotenzial besteht. Viele andere werden KI-basierte Coding-Tools in einem ähnlichen Ausmaß nutzen wie IDEs – und damit (hoffentlich) ihr Grundlagenwissen (nicht nur wenn es um JavaScript geht) weiter ausbauen. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!

Wer die Grundlagen von modernem JavaScript aus dem Effeff beherrscht, erzielt schneller bessere Ergebnisse.Gorodenkoff | shutterstock.com JavaScript ist eine unglaublich robuste, vielseitige und leistungsfähige Sprache, die oft schon standardmäßig alles bietet, was Entwickler brauchen. Der Schlüssel zum Erfolg liegt jedoch darin, das gesamte Spektrum der Programmiersprache zu kennen – und zu wissen, welche Aspekte, beziehungsweise Konzepte die eigene Applikation bereichern können.   In diesem Artikel lesen Sie, wie Sie als Developer das Maximum aus den heute in JavaScript verfügbaren Tools und Bibliotheken herausholen. 1. Variablendeklarationen verwenden Variablen sind so alt wie das Programmieren selbst – und stellen auch in modernem JavaScript noch immer ein Schlüsselkonzept dar. In diesem Zusammenhang sollten wir uns zunächst vergegenwärtigen, dass bei JavaScript const den Vorzug vor let erhält – und uns fragen, warum das so ist. Mit const wird eine Konstante deklariert, also eine Variable, die sich nicht verändert. Entsprechend nutzen wir const wann immer es möglich ist, denn seine Unveränderlichkeit (Immutability) macht es weniger komplex. Sie müssen sich bei einer Konstante keine Gedanken darüber machen, wie sie sich verhält oder sich im Programmverlauf verändern könnte. So ermöglicht const, einen Wert zu speichern und überall bedenkenlos zu verwenden. Das Konzept der Immutability ist ein tiefgreifendes, das sich ganz allgemein im Software Design widerspiegelt – insbesondere aber in der funktionalen und reaktiven Programmierung: Hier dient es dazu, die Gesamtstruktur größerer Systeme zu vereinfachen. Ein weiterer wichtiger Aspekt von const ist seine Funktionsweise im Fall von Objekten und Sammlungen. In diesen Fällen verhindert es, dass die Referenz auf die Variable geändert wird (allerdings nicht, dass deren interner Zustand verändert wird). Das offenbart Wichtiges über die interne Struktur von JavaScript: Unter der Haube sind Objekt- und Sammlungsvariablen Pointer – sie belegen also Speicherplatz. Wenn Sie sich dazu entscheiden, const zu verwenden, können Sie das nicht ändern. Natürlich gibt es auch Fälle, in denen wir eine Variable benötigen, die wirklich eine Variable ist. In diesem Fall kommt let zum Zug – obwohl JavaScript auch über das Keyword var verfügt. Den Unterschied zwischen let und var zu kennen, kann Ihnen dabei helfen, Variable Scoping zu durchdringen, was wiederum für fortgeschrittenere Konzepte (dazu gleich mehr) von Bedeutung ist. Wird let deklariert, beschränkt das die Variable auf den Block, in dem sie deklariert ist, während var sie in den übergeordneten Geltungsbereich „hochzieht“. Dadurch ist var besser sichtbar, aber auch fehleranfälliger. Es empfiehlt sich deshalb mit Blick auf Ihren Code, var durch let zu ersetzen. 2. Collections und Operators verstehen Funktionale Operatoren zählen zu den leistungsstärksten Funktionen von modernem JavaScript: Mit map, flatMap, reduce oder forEach wird es möglich, Repetitions über Collections mit einer übersichtlichen, selbsterklärenden Syntax auszuführen. Diese funktionalen Programmierkonstrukte machen Code besser, respektive direkt lesbar. Wenn Sie ein Programm schreiben, versuchen Sie in der Regel, eine bestimmte Business-Funktion umzusetzen – beispielsweise Rückmeldung von einer API zu erhalten und diese auf Grundlage von Benutzereingaben zu verarbeiten. Für diesen Task benötigen Sie einen Loop. Dieser ist aber nur ein notwendiger Teil der Logik und sollte nicht zu viel Platz im Programm einnehmen. Funktionale Operatoren lassen sich nutzen, um den Loop zu beschreiben, ohne die übergeordnete Bedeutung zu verschleiern. Ein Beispiel: const albums = [ { artist: “Keith Jarrett”, album: “The Köln Concert”, genre: “Jazz” }, { artist: “J.S. Bach”, album: “Brandenburg Concertos”, genre: “Classical” }, { artist: “The Beatles”, album: “Abbey Road”, genre: “Rock” }, { artist: “Beastie Boys”, album: “Ill Communication”, genre: “Hip Hop”}]; genreInput = “rock”; console.log( albums.filter(album => album.genre.toLowerCase() === genreInput.toLowerCase()) ) Allgemein soll der obenstehende Code bewirken, eine Liste von Musikalben nach Genre zu filtern. Die integrierte Methode filter für das Array albums gibt eine neue Sammlung zurück, auf die die übergebene Funktion angewendet wurde. Die Looping-Logik wurde auf das Wesentliche reduziert, um den Sinn des Codes besser hervorzuheben. An dieser Stelle ist jedoch anzumerken, dass traditionelle Loops nach wie vor eine wichtige Rolle spielen. Insbesondere, wenn diese sehr komplex ausfallen und mehrere Iteratoren beinhalten oder es um enorm große Loop Bodies geht. 3. Promises nutzen Asynchron zu programmieren ist von Natur aus schwierig, weil dabei per Definition mehrere Aktionen gleichzeitig ablaufen. Das hat zur Folge, sich mit der sich mit der Verflechtung von Events befassen zu müssen. Glücklicherweise verfügt JavaScript über starke Abstraktionen für diese Konzepte. Promises sind die erste Verteidigungslinie, wenn es darum geht, asynchrone Komplexitäten zu managen – und die Keywords async/await bieten eine weitere, zusätzliche Ebene, die es ermöglicht, asynchrone Operationen in einer synchron aussehenden Syntax zu schreiben. Als Softwareentwickler nutzen Sie Promises oder asynchrone Funktionen in Bibliotheken. Die allgegenwärtige fetch-Funktion, die in den Browser (und auch in serverseitige Plattformen wie Node) integriert ist, ist dafür ein gutes Beispiel: async function getStarWarsPerson(personId) { const response = await fetch(`https://swapi.dev/api/people/${personId}/`); if (response.ok) { // … } Die von uns definierte Funktion enthält async, während die verwendete Funktion (fetch) mit await modifiziert wird. Das sieht aus wie eine normale Zeile mit synchronem Code, ermöglicht jedoch, dass fetch zu einem anderen Zeitpunkt ausgeführt wird – gefolgt von was auch immer als Nächstes kommt. Diese Sequenz „befreit“ den Event Loop, der sich um andere Dinge kümmern kann, während fetch läuft.   Promises sind nicht allzu schwer zu verstehen, aber sie führen Sie tiefer in die Semantik tatsächlicher, asynchroner Operationen ein. Die Idee: Ein Promise-Objekt repräsentiert eine asynchrone Operation, seine resolve– und reject-Methoden das Ergebnis. Der Client-Code verarbeitet die Resultate schließlich mit den Callback-Methoden then() und catch(). Zu beachten ist dabei, dass JavaScript nicht wirklich „concurrent“ ist: Es verwendet asynchrone Konstrukte, um Parallelität zu unterstützen, aber es gibt nur einen Event Loop, der einen einzelnen Betriebssystem-Thread repräsentiert. 4. Shortcuts kennen Die leistungsstarken Shortcuts von JavaScript können die Developer Experience entscheidend optimieren. Die folgenden fünf Operatoren reduzieren einige der gängigsten und umständlichsten Aspekte der Programmierarbeit mit JavaScript auf einige wenige Tastenanschläge.   Spread Mit dem Spread-Operator können Sie auf einzelne Elemente eines Arrays oder Objekts verweisen: const originalArray = [1, 2, 3]; const copiedArray = […originalArray]; copiedArray.push(‘foo’); // [1,2,3,’foo’] Das funktioniert auch für Objekte: const person = { name: “Alice”, age: 30 }; const address = { city: “New York”, country: “USA” }; const fullInfo = { …person, …address }; Destruct Destructing bietet Ihnen eine prägnante Möglichkeit, die Elemente eines Arrays oder Objekts in ihre Bestandteile zu „zerlegen“: const colors = [“red”, “green”, “blue”]; const [firstColor] = colors; firstColor === “red”; const person = { name: “Alice”, age: 30, city: “London” }; const { city } = person; city === “London”; Diese elegante Syntax kommt besonders häufig zum Einsatz, um Module zu importieren: const express = require(‘express’); const { json, urlencoded } = require(‘express’); Destructing unterstützt darüber hinaus auch benannte Parameter und Standardwerte. Optional Chaining Die alte Praxis manueller Null-Checks wird mit Optional Chaining durch einen einzigen, übersichtlichen Operator ersetzt: const street = user?.profile?.address?.street; Ist einer der Roots und Branches in dieser Dot-Access-Chain null, wird das gesamte Konstrukt zu Null aufgelöst (anstatt eine Nullpointer Exception auszulösen). Logical Assignment Logische Zuweisungen gibt es in den Nullish-Varianten and, or sowie strict. Die letztgenannte Variante: let myString = null; myString ??= “Foo”; myString ??= “Bar”; myString === “Foo”; Hierbei ist zu beachten, dass sich myString nur ändert, wenn es tatsächlich null (oder undefiniert) ist. Nullish Coalescence Mit Nullish Coalescence können Sie ganz einfach zwischen einer Variablen, die möglicherweise null ist, und einem Standardwert wählen: let productName = null; let displayName = productName ?? “Unknown Product”; productName === “Unknown Product”; 5. Scopes und Closures nicht fürchten Mit Blick auf die JavaScript-Grundlagen sind Scopes und Closures bedeutende Konzepte. Der Begriff „Scope“ ist davon abgesehen in allen Programmiersprachen zentral. Er bezieht sich einfach auf den Sichtbarkeitsbereich einer Variablen. Es geht also um die Frage(n): Wo kann eine Variable genutzt werden, sobald sie deklariert wurde – und wo ist sie sichtbar? Closure bezeichnet hingegen die Art und Weise, wie der Geltungsbereich einer Variablen unter besonderen Umständen funktioniert. Wird ein neuer Funktions-Scope deklariert, werden die Variablen im umgebenden Kontext für diesen verfügbar gemacht. Im Grunde also ein simples Konzept, dessen Namensgebug darauf beruht, dass der umgebende Scope den inneren „umschließt“. Closures haben weitreichende Auswirkungen: Sie können sie verwenden, um Variablen zu definieren, die für den größeren Kontext von Bedeutung sind – und dann Funktionsblöcke definieren, die auf diese Variablen angewendet werden (wodurch Ihre Logik stark eingeschränkt oder gekapselt wird). Das Konzept in Pseudocode: outer context variable x function context do stuff with x x now reflects changes The same idea in JS: function outerFunction() { let x = 10; function innerFunction() { x = 20; } innerFunction(); console.log(x); // Outputs 20 } outerFunction(); In diesem Beispiel ist innerFunction() eine Closure: Sie greift auf die Variable über ihren übergeordneten Bereich zu (auch als lexikalischer Scope bezeichnet). Die Closure hat also Zugriff auf die Variablen des Scops, indem sie deklariert wird – nicht in dem, in dem sie gecallt wird. Wie bereits erwähnt, ist Unveränderlichkeit ein Grundsatz der funktionalen Programmierung. Für ein sauberes Design ist es also zu vermeiden, Variablen zu ändern. In unserem Beispiel verstößt die Änderung von x gegen diese Richtlinie. Der Zugriff auf diese Variable ist jedoch eine wesentliche Funktion. Diese Art und Weise, Closures einzusetzen, ist bei funktionalen Collection-Operatoren wie map und reduce noch wichtiger. Sie bieten eine saubere Syntax, um Aufgaben auszuführen und haben außerdem Zugriff auf den lexikalischen Scope, in dem sie deklariert sind. 6. Fehler behandeln Error Handling wird uns als Programmierer wohl für den Rest unserer Tage begleiten. Zum Glück ist die Fehlerbehandlung bei modernem JavaScript inzwischen dank zahlreicher Optimierungen relativ ausgereift. Es gibt zwei grundlegende Arten von Fehlern: normale, synchrone Codefehler und asynchrone Ereignisfehler. Fehlerobjekte enthalten Fehlermeldungen und Ursachenobjekte sowie Stacktraces, also Auszüge aus dem Call Stack zum Zeitpunkt des Fehlers. Der Hauptmechanismus für normale Fehler ist der gute alte try-catch-finally-Block und sein Ursprung, das Schlüsselwort throw. Im Fall von asynchronen Fehlern wird es etwas kniffliger: Zwar ist die Syntax mit catch-Callbacks und reject-Aufrufen für Promises sowie Catch-Blöcken für asynchrone Funktionen nicht allzu komplex. Diese asynchronen Aufrufe gilt es jedoch im Auge zu behalten und sicherzustellen, dass sämtliche Fehler behandelt werden. 7. Programmierstil richtig wählen Kluge JavaScript-Entwickler sind offen für verschiedene Programmierparadigmen. Schließlich unterstützt JavaScript sowohl objektorientierte als auch funktionale, imperative und reaktive Programmierung. Es gibt keinen (vernünftigen) Grund, diese Möglichkeiten nicht zu nutzen. Sie können Ihr Programm entweder um einen dieser Stile herum aufbauen oder (je nach Use Case) auch mehrere verschiedene miteinander vermengen. Das ist typisch für JavaScript: Es gibt mehr als einen Weg zum Ziel. 8. KI-Unterstützung abwägen KI-Programmierassistenten gibt es noch nicht allzu lange – und doch sind sie als Unterstützungsleistung für viele Entwickler fast nicht mehr wegzudenken. Allerdings gilt das auch für moderne IDEs. Und auch wenn man denken könnte, dass kein Dev ohne VS Code, IntelliJ oder Eclipse auskommt, sieht die Realität mancherorts anders aus. Es gibt nämlich auch Entwickler, die die Posix-Befehlszeile zusammen mit Vim oder Emacs so einsetzen, dass eine visuelle IDE mit Maus im Vergleich dazu eher klobig wirkt. Ebenso wird es weiterhin einige Profis geben, die ohne KI effektiver programmieren. Das liegt allerdings im Wesentlichen daran, dass sie die Grundlagen perfekt beherrschen und selbst in komplexen Systemen direkt erkennen, wo Optimierungspotenzial besteht. Viele andere werden KI-basierte Coding-Tools in einem ähnlichen Ausmaß nutzen wie IDEs – und damit (hoffentlich) ihr Grundlagenwissen (nicht nur wenn es um JavaScript geht) weiter ausbauen. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox! 

Nach oben scrollen
×