JavaScript-Sprachübersicht

JavaScript ist eine multiparadigmatische, dynamische Sprache mit Typen und Operatoren, standardmäßigen eingebauten Objekten und Methoden. Ihre Syntax basiert auf den Sprachen Java und C – viele Strukturen aus diesen Sprachen gelten auch für JavaScript. JavaScript unterstützt objektorientierte Programmierung mit Objektprototypen und Klassen. Es unterstützt auch funktionale Programmierung, da Funktionen First-Class Objekte sind, die leicht über Ausdrücke erstellt und wie jedes andere Objekt weitergegeben werden können.

Diese Seite dient als kurze Übersicht über verschiedene JavaScript-Sprachmerkmale, geschrieben für Leser mit Hintergrund in anderen Sprachen wie C oder Java.

Datentypen

Beginnen wir mit den Bausteinen jeder Sprache: den Typen. JavaScript-Programme manipulieren Werte, und alle diese Werte gehören zu einem Typ. JavaScript bietet sieben primitive Typen:

  • Number: Wird für alle Zahlenwerte (Ganzzahlen und Gleitkommazahlen) außer für sehr große Ganzzahlen verwendet.
  • BigInt: Wird für beliebig große Ganzzahlen verwendet.
  • String: Zum Speichern von Text verwendet.
  • Boolean: true und false – üblicherweise für bedingte Logik verwendet.
  • Symbol: Zum Erstellen von eindeutigen Bezeichnern, die nicht kollidieren, verwendet.
  • Undefined: Zeigt an, dass einer Variablen kein Wert zugewiesen wurde.
  • Null: Zeigt einen absichtlichen Nicht-Wert an.

Alles andere wird als Objekt bekannt. Häufige Objekttypen sind:

Funktionen sind in JavaScript keine speziellen Datenstrukturen – sie sind nur ein spezieller Typ von Objekt, das aufgerufen werden kann.

Zahlen

JavaScript verfügt über zwei eingebaute numerische Typen: Number und BigInt.

Der Number-Typ ist ein IEEE 754 64-Bit Double-Precision Gleitkommawert, was bedeutet, dass Ganzzahlen sicher zwischen -(253 − 1) und 253 − 1 dargestellt werden können, ohne Präzisionsverlust. Gleitkommazahlen können bis zu 1,79 × 10308 gespeichert werden. In Zahlen unterscheidet JavaScript nicht zwischen Gleitkommazahlen und Ganzzahlen.

js
console.log(3 / 2); // 1.5, not 1

Ein scheinbarer Integer ist in der Tat implizit ein Float. Aufgrund der IEEE 754-Codierung kann die Gleitkomma-Arithmetik manchmal ungenau sein.

js
console.log(0.1 + 0.2); // 0.30000000000000004

Für Operationen, die Ganzzahlen erwarten, wie Bit-Operationen, wird die Zahl in eine 32-Bit-Ganzzahl umgewandelt.

Zahlliterale können auch Präfixe haben, um die Basis (binär, oktal, dezimal oder hexadezimal) anzugeben, oder ein Exponentsuffix.

js
console.log(0b111110111); // 503
console.log(0o767); // 503
console.log(0x1f7); // 503
console.log(5.03e2); // 503

Der BigInt-Typ ist ein beliebig langes Integer. Sein Verhalten ähnelt den Integer-Typen in C (z.B. wird bei der Division zu Null gekürzt), außer dass er sich unbegrenzt vergrößern kann. BigInts werden mit einem Zahlenliteral und einem n-Suffix spezifiziert.

js
console.log(-3n / 2n); // -1n

Die Standard-Arithmetikoperatoren werden unterstützt, einschließlich Addition, Subtraktion, Restarithmetik, etc. BigInts und Zahlen können in arithmetischen Operationen nicht gemischt werden.

Das Math-Objekt stellt standardmäßige mathematische Funktionen und Konstanten bereit.

js
Math.sin(3.5);
const circumference = 2 * Math.PI * r;

Es gibt drei Möglichkeiten, eine Zeichenkette in eine Zahl umzuwandeln:

  • parseInt(), das die Zeichenkette für eine Ganzzahl parst.
  • parseFloat(), das die Zeichenkette für eine Gleitkommazahl parst.
  • Die Number()-Funktion, die eine Zeichenkette parst, als wäre sie ein Zahlenliteral, und viele verschiedene Zahlenrepräsentationen unterstützt.

Sie können auch das unäre Plus + als Kurzform für Number() verwenden.

Zahlenwerte beinhalten auch NaN (kurz für "Not a Number") und Infinity. Viele "ungültige mathematische" Operationen geben NaN zurück – zum Beispiel beim Versuch, eine nicht-numerische Zeichenkette zu parsen oder Math.log() bei einem negativen Wert zu verwenden. Division durch Null erzeugt Infinity (positiv oder negativ).

NaN ist ansteckend: Wenn Sie es als Operand einer mathematischen Operation bereitstellen, wird das Ergebnis auch NaN sein. NaN ist der einzige Wert in JavaScript, der nicht gleich sich selbst ist (gemäß IEEE 754-Spezifikation).

Zeichenketten

Zeichenketten in JavaScript sind Sequenzen von Unicode-Zeichen. Dies sollte für jeden, der sich mit Internationalisierung befassen musste, eine willkommene Nachricht sein. Genau genommen sind sie UTF-16-codiert.

js
console.log("Hello, world");
console.log("你好,世界!"); // Nearly all Unicode characters can be written literally in string literals

Zeichenketten können entweder mit einfachen oder doppelten Anführungszeichen geschrieben werden – JavaScript unterscheidet nicht zwischen Zeichen und Zeichenketten. Wenn Sie ein einzelnes Zeichen darstellen möchten, verwenden Sie einfach eine Zeichenkette, die aus diesem einen Zeichen besteht.

js
console.log("Hello"[1] === "e"); // true

Um die Länge einer Zeichenkette (in Code-Einheiten) zu ermitteln, rufen Sie ihre length-Eigenschaft auf.

Zeichenketten haben Hilfsmethoden, um die Zeichenkette zu manipulieren und Informationen über die Zeichenkette zu erhalten. Da alle Primitiven von Design her unveränderlich sind, geben diese Methoden neue Zeichenketten zurück.

Der +-Operator ist für Zeichenketten überladen: Wenn einer der Operanden eine Zeichenkette ist, wird Zeichenkettenverkettung statt Zahladdition durchgeführt. Eine spezielle Template-Literal- Syntax ermöglicht es Ihnen, Zeichenketten mit eingebetteten Ausdrücken prägnanter zu schreiben. Im Gegensatz zu Pythons f-Strings oder C#'s interpolierten Zeichenketten verwenden Template-Literale Backticks (nicht einfache oder doppelte Anführungszeichen).

js
const age = 25;
console.log("I am " + age + " years old."); // String concatenation
console.log(`I am ${age} years old.`); // Template literal

Andere Typen

JavaScript unterscheidet zwischen null, das einen absichtlichen Nicht-Wert anzeigt (und nur über das null-Schlüsselwort zugänglich ist), und undefined, das das Fehlen von Werten anzeigt. Es gibt viele Möglichkeiten, undefined zu erhalten:

  • Eine return-Anweisung ohne Wert (return;) gibt implizit undefined zurück.
  • Der Zugriff auf eine nicht existierende Objekt- Eigenschaft (obj.iDontExist) gibt undefined zurück.
  • Eine Variablen-Deklaration ohne Initialisierung (let x;) wird die Variable implizit auf undefined initialisieren.

JavaScript hat einen Boolean-Typ, mit den möglichen Werten true und false – beide sind Schlüsselwörter. Jeder Wert kann gemäß den folgenden Regeln in einen Boolean umgewandelt werden:

  1. false, 0, leere Zeichenketten (""), NaN, null und undefined werden alle zu false.
  2. Alle anderen Werte werden zu true.

Sie können diese Konvertierung explizit mit der Boolean()-Funktion durchführen:

js
Boolean(""); // false
Boolean(234); // true

Dies ist jedoch selten notwendig, da JavaScript diese Konvertierung stillschweigend durchführt, wenn es einen Boolean erwartet, wie in einer if-Anweisung (siehe Steuerstrukturen). Aus diesem Grund sprechen wir manchmal von "truthy" und "falsy", was bedeutet, dass Werte in Booleschen Kontexten zu true und false werden.

Boolesche Operationen wie && (logisches und), || (logisches oder) und ! (logisches nicht) werden unterstützt; siehe Operatoren.

Der Symboltyp wird häufig verwendet, um eindeutige Bezeichner zu erstellen. Jedes Symbol, das mit der Symbol()-Funktion erstellt wird, ist garantiert eindeutig. Zusätzlich gibt es registrierte Symbole, die geteilte Konstanten sind, und bekannte Symbole, die von der Sprache als "Protokolle" für bestimmte Operationen verwendet werden. Sie können mehr darüber in der Symbol-Referenz lesen.

Variablen

Variablen in JavaScript werden mit einem von drei Schlüsselwörtern deklariert: let, const oder var.

let ermöglicht die Deklaration von Blockvariablen. Die deklarierte Variable ist ab dem umschlossenen Block verfügbar.

js
let a;
let name = "Simon";

// myLetVariable is *not* visible out here

for (let myLetVariable = 0; myLetVariable < 5; myLetVariable++) {
  // myLetVariable is only visible in here
}

// myLetVariable is *not* visible out here

const ermöglicht es Ihnen, Variablen zu deklarieren, deren Werte niemals geändert werden sollen. Die Variable ist ab dem in Block deklarierten Abschnitt verfügbar.

js
const Pi = 3.14; // Declare variable Pi
console.log(Pi); // 3.14

Eine mit const deklarierte Variable kann nicht neu zugewiesen werden.

js
const Pi = 3.14;
Pi = 1; // will throw an error because you cannot change a constant variable.

const-Deklarationen verhindern nur Neuzuordnungen – sie verhindern nicht Mutationen des Variablenwerts, wenn es sich um ein Objekt handelt.

js
const obj = {};
obj.a = 1; // no error
console.log(obj); // { a: 1 }

var-Deklarationen können unerwartetes Verhalten haben (zum Beispiel sind sie nicht block-scope), und sie werden im modernen JavaScript-Code nicht empfohlen.

Wenn Sie eine Variable deklarieren, ohne ihr einen Wert zuzuweisen, hat sie den Wert undefined. Sie können keine const-Variable ohne Initialisierung deklarieren, da Sie sie später sowieso nicht mehr ändern können.

Mit let und const deklarierte Variablen belegen den gesamten Gültigkeitsbereich, in dem sie definiert sind, und befinden sich in einer bekannten Region als temporal dead zone, bevor die tatsächliche Deklarationszeile durchlaufen wird. Dies hat einige interessante Interaktionen mit Variablenschatten, die in anderen Sprachen nicht vorkommen.

js
function foo(x, condition) {
  if (condition) {
    console.log(x);
    const x = 2;
    console.log(x);
  }
}

foo(1, true);

In den meisten anderen Sprachen würde dies "1" und "2" ausgeben, da vor der Zeile const x = 2 x immer noch auf den Parameter x im oberen Gültigkeitsbereich verweisen sollte. In JavaScript würde dies aufgrund der Tatsache, dass jede Deklaration den gesamten Gültigkeitsbereich belegt, bei der ersten console.log-Zeile einen Fehler auslösen: "Cannot access 'x' before initialization". Weitere Informationen finden Sie auf der Referenzseite von let.

JavaScript ist dynamisch typisiert. Typen (wie in dem vorherigen Abschnitt beschrieben) sind nur mit Werten, jedoch nicht mit Variablen verbunden. Für let-deklarierte Variablen können Sie deren Typ durch Neuzuordnung immer ändern.

js
let a = 1;
a = "foo";

Operatoren

JavaScripts numerische Operatoren umfassen +, -, *, /, % (Rest) und ** (Exponenzierung). Werte werden unter Verwendung von = zugewiesen. Jeder binäre Operator hat auch ein zusammengesetztes Zuweisungsgegenstück wie += und -=, das sich zu x = x operator y erweitert.

js
x += 5;
x = x + 5;

Sie können ++ und -- zum Inkrementieren und Dekrementieren verwenden. Diese können als Präfix- oder Postfix-Operatoren verwendet werden.

Der +-Operator führt auch eine Zeichenkettenverkettung durch:

js
"hello" + " world"; // "hello world"

Wenn Sie eine Zeichenkette zu einer Zahl (oder einem anderen Wert) hinzufügen, wird alles zuerst in eine Zeichenkette konvertiert. Dies könnte Sie möglicherweise aus dem Tritt bringen:

js
"3" + 4 + 5; // "345"
3 + 4 + "5"; // "75"

Das Hinzufügen einer leeren Zeichenkette zu etwas ist eine nützliche Möglichkeit, es selbst in eine Zeichenkette zu konvertieren.

Vergleiche in JavaScript können mit <, >, <= und >= durchgeführt werden, die sowohl für Zeichenketten als auch für Zahlen funktionieren. Der Doppelgleichheitsoperator führt eine Typumwandlung durch, wenn Sie ihm unterschiedliche Typen übergeben, was teilweise interessante Ergebnisse liefern kann. Andererseits versucht der Dreifachgleichheitsoperator keine Typumwandlung, und wird normalerweise bevorzugt.

js
123 == "123"; // true
1 == true; // true

123 === "123"; // false
1 === true; // false

Der Doppelgleichheits- und Dreifachgleichheitsoperator haben auch ihre Gegenstücke zur Ungleichheit: != und !==.

JavaScript hat auch Bitoperationen und Logikoperationen. Bemerkenswerterweise funktionieren Logikoperatoren nicht nur mit Booleschen Werten – sie funktionieren nach der "Wahrheitsgehalt" des Werts.

js
const a = 0 && "Hello"; // 0 because 0 is "falsy"
const b = "Hello" || "world"; // "Hello" because both "Hello" and "world" are "truthy"

Die &&- und ||-Operatoren verwenden eine Kurzschlusslogik, was bedeutet, dass sie ihren zweiten Operanden nur ausführen, wenn der erste wahr ist. Dies ist nützlich, um auf Null-Objekte zu prüfen, bevor Sie ihre Attribute aufrufen:

js
const name = o && o.getName();

Oder um Werte zu cachen (wenn falsche Werte ungültig sind):

js
const name = cachedName || (cachedName = getName());

Für eine umfassende Liste der Operatoren siehe die Leitfadenseite oder den Referenzabschnitt. Möglicherweise sind Sie besonders an der Operator-Priorität interessiert.

Grammatik

Die JavaScript-Grammatik ähnelt sehr der C-Familie. Es gibt einige Punkte, die erwähnt werden sollten:

  • Bezeichner können Unicode-Zeichen haben, aber sie können nicht eines der reservierten Wörter sein.
  • Kommentare sind gewöhnlich // oder /* */, während viele andere Skriptsprachen wie Perl, Python und Bash # verwenden.
  • Semikolons sind in JavaScript optional – die Sprache fügt sie automatisch ein, wenn erforderlich. Allerdings gibt es bestimmte Vorbehalte, die beachtet werden müssen, da Semikolons im Gegensatz zu Python dennoch Teil der Syntax sind.

Für einen tiefgehenden Blick auf die JavaScript-Grammatik siehe die Referenzseite zur lexikalischen Grammatik.

Steuerstrukturen

JavaScript hat eine ähnliche Gruppe von Steuerstrukturen wie andere Sprachen der C-Familie. Bedingte Anweisungen werden durch if und else unterstützt; Sie können sie miteinander verketten:

js
let name = "kittens";
if (name === "puppies") {
  name += " woof";
} else if (name === "kittens") {
  name += " meow";
} else {
  name += "!";
}
name === "kittens meow";

JavaScript hat kein elif, und else if ist wirklich nur ein else-Zweig, der aus einer einzelnen if-Anweisung besteht.

JavaScript hat while-Schleifen und do...while-Schleifen. Die erste eignet sich für grundlegende Schleifen; die zweite für Schleifen, bei denen sichergestellt werden soll, dass der Schleifenkörper mindestens einmal ausgeführt wird:

js
while (true) {
  // an infinite loop!
}

let input;
do {
  input = get_input();
} while (inputIsNotValid(input));

Die for-Schleife von JavaScript ist dieselbe wie in C und Java: Sie ermöglicht die Angabe der Steuerinformationen für Ihre Schleife in einer einzigen Zeile.

js
for (let i = 0; i < 5; i++) {
  // Will execute 5 times
}

JavaScript enthält auch zwei weitere bedeutende Schleifen: for...of, das über iterables iteriert, insbesondere Arrays, und for...in, das alle enumerierbaren Eigenschaften eines Objekts besucht.

js
for (const value of array) {
  // do something with value
}

for (const property in object) {
  // do something with object property
}

Die switch-Anweisung kann für mehrere Zweige basierend auf Gleichheitsprüfungen verwendet werden.

js
switch (action) {
  case "draw":
    drawIt();
    break;
  case "eat":
    eatIt();
    break;
  default:
    doNothing();
}

Ähnlich wie in C sind Fallklammern konzeptionell dasselbe wie Label, sodass, wenn Sie keine break-Anweisung hinzufügen, die Ausführung zur nächsten Ebene "durchfallen" wird. Allerdings sind sie eigentlich keine Sprungtabellen – jeder Ausdruck kann Teil der case-Anweisung sein, nicht nur Zeichenfolgen- oder Zahlenliterale, und sie werden einzeln ausgewertet, bis eins dem zu vergleichenden Wert entspricht. Der Vergleich erfolgt zwischen den beiden unter Verwendung des ===-Operators.

Im Gegensatz zu einigen Sprachen wie Rust sind Kontrollflussstrukturen in JavaScript Anweisungen, was bedeutet, dass Sie sie nicht einer Variablen zuweisen können, wie const a = if (x) { 1 } else { 2 }.

JavaScript-Fehler werden mit der try...catch-Anweisung behandelt.

js
try {
  buildMySite("./website");
} catch (e) {
  console.error("Building site failed:", e);
}

Fehler können mit der throw-Anweisung ausgelöst werden. Viele eingebaute Operationen können ebenfalls werfen.

js
function buildMySite(siteDirectory) {
  if (!pathExists(siteDirectory)) {
    throw new Error("Site directory does not exist");
  }
}

Im Allgemeinen können Sie den Typ des gerade abgefangenen Fehlers nicht erkennen, da alles von einer throw-Anweisung geworfen werden kann. Sie können jedoch normalerweise annehmen, dass es sich um eine Instanz von Error handelt, wie im obigen Beispiel. Es gibt einige eingebaute Unterklassen von Error, wie TypeError und RangeError, die Sie verwenden können, um zusätzliche Semantik über den Fehler bereitzustellen. Es gibt keinen bedingten Catch in JavaScript – wenn Sie nur einen bestimmten Fehler behandeln möchten, müssen Sie alles abfangen, den Fehlertyp mit instanceof identifizieren und dann die anderen Fälle erneut werfen.

js
try {
  buildMySite("./website");
} catch (e) {
  if (e instanceof RangeError) {
    console.error("Seems like a parameter is out of range:", e);
    console.log("Retrying...");
    buildMySite("./website");
  } else {
    // Don't know how to handle other error types; throw them so
    // something else up in the call stack may catch and handle it
    throw e;
  }
}

Wenn ein Fehler von keinem try...catch im Aufrufstapel abgefangen wird, wird das Programm beendet.

Für eine umfassende Liste der Kontrollflussanweisungen siehe den Referenzabschnitt.

Objekte

JavaScript-Objekte können als Sammlungen von Schlüssel-Wert-Paaren betrachtet werden. Als solche sind sie ähnlich wie:

  • Dictionaries in Python.
  • Hashes in Perl und Ruby.
  • Hashtabellen in C und C++.
  • HashMaps in Java.
  • Assoziative Arrays in PHP.

JavaScript-Objekte sind Hashes. Im Gegensatz zu Objekten in statisch typisierten Sprachen haben Objekte in JavaScript keine festen Formen – Eigenschaften können jederzeit hinzugefügt, gelöscht, neu angeordnet, verändert oder dynamisch abgefragt werden. Objektschlüssel sind immer Zeichenketten oder Symbole – selbst Array-Indizes, die im Kanon Ganzzahlen sind, sind tatsächlich unter der Haube Zeichenketten.

Objekte werden normalerweise mit der Literal-Syntax erstellt:

js
const obj = {
  name: "Carrot",
  for: "Max",
  details: {
    color: "orange",
    size: 12,
  },
};

Objekteigenschaften können mit Punkt (.) oder eckigen Klammern ([]) abgerufen werden. Wenn Sie die Punktnotation verwenden, muss der Schlüssel ein gültiger Bezeichner sein. Eckige Klammern hingegen ermöglichen das Indizieren des Objekts mit einem dynamischen Schlüsselwert.

js
// Dot notation
obj.name = "Simon";
const name = obj.name;

// Bracket notation
obj["name"] = "Simon";
const name = obj["name"];

// Can use a variable to define a key
const userName = prompt("what is your key?");
obj[userName] = prompt("what is its value?");

Eigenschaftszugriffe können verkettet werden:

js
obj.details.color; // orange
obj["details"]["size"]; // 12

Objekte sind immer Referenzen, sodass, wenn nichts explizit das Objekt kopiert, Mutationen eines Objekts für die Außenwelt sichtbar sind.

js
const obj = {};
function doSomething(o) {
  o.x = 1;
}
doSomething(obj);
console.log(obj.x); // 1

Dies bedeutet auch, dass zwei separat erstellte Objekte niemals gleich (!==) sind, weil sie unterschiedliche Referenzen sind. Wenn Sie zwei Referenzen des gleichen Objekts halten, würde das ändern eines, durch das andere beobachtbar sein.

js
const me = {};
const stillMe = me;
me.x = 1;
console.log(stillMe.x); // 1

Für mehr zu Objekten und Prototypen, siehe die Object-Referenzseite. Für mehr Informationen zur Objektinitialisierer-Syntax lesen Sie die Referenzseite.

Diese Seite hat alle Details zu Objektprototypen und Vererbung ausgelassen, da Sie normalerweise Vererbung mit Klassen erreichen können, ohne den zugrunde liegenden Mechanismus zu berühren (der als schwierig bekannt ist). Um mehr darüber zu erfahren, siehe Vererbung und die Prototypenkette.

Arrays

Arrays in JavaScript sind eigentlich eine spezielle Art von Objekt. Sie funktionieren sehr ähnlich wie reguläre Objekte (numerische Eigenschaften können natürlich nur mithilfe der []-Syntax abgerufen werden), aber sie haben eine magische Eigenschaft namens length. Diese ist immer um eins größer als der höchste Index im Array.

Arrays werden normalerweise mit Array-Literalen erstellt:

js
const a = ["dog", "cat", "hen"];
a.length; // 3

JavaScript-Arrays sind nach wie vor Objekte – Sie können ihnen beliebige Eigenschaften zuweisen, einschließlich beliebiger Indexnummern. Das einzige "magische" daran ist, dass length automatisch aktualisiert wird, wenn Sie einen bestimmten Index setzen.

js
const a = ["dog", "cat", "hen"];
a[100] = "fox";
console.log(a.length); // 101
console.log(a); // ['dog', 'cat', 'hen', empty × 97, 'fox']

Das oben erhaltene Array wird ein lückenhaftes Array genannt, da es unbewohnte Slots in der Mitte gibt und die Engine veranlassen wird, es von einem Array in eine Hashtabelle zu optimieren. Stellen Sie sicher, dass Ihr Array dicht bevölkert ist!

Ein Index außerhalb des Bereichs wirft keinen Fehler. Wenn Sie einen nicht existierenden Array-Index abfragen, erhalten Sie den Wert undefined zurück:

js
const a = ["dog", "cat", "hen"];
console.log(typeof a[90]); // undefined

Arrays können beliebige Elemente haben und sich beliebig vergrößern oder verkleinern.

js
const arr = [1, "foo", true];
arr.push({});
// arr = [1, "foo", true, {}]

Arrays können mit der for-Schleife iteriert werden, wie in anderen C-ähnlichen Sprachen:

js
for (let i = 0; i < a.length; i++) {
  // Do something with a[i]
}

Oder, da Arrays iterierbar sind, können Sie die for...of-Schleife verwenden, die der for (int x : arr)-Syntax in C++/Java entspricht:

js
for (const currentValue of a) {
  // Do something with currentValue
}

Arrays haben eine Vielzahl von Array-Methoden. Viele von ihnen würden das Array iterieren – beispielsweise würde map() eine Rückruffunktion auf jedes Array-Element anwenden und ein neues Array zurückgeben:

js
const babies = ["dog", "cat", "hen"].map((name) => `baby ${name}`);
// babies = ['baby dog', 'baby cat', 'baby hen']

Funktionen

Zusammen mit Objekten sind Funktionen der Kernbestandteil, um JavaScript zu verstehen. Die grundlegendste Funktionsdeklaration sieht so aus:

js
function add(x, y) {
  const total = x + y;
  return total;
}

Eine JavaScript-Funktion kann 0 oder mehr Parameter nehmen. Der Funktionskörper kann so viele Anweisungen enthalten, wie Sie möchten, und kann seine eigenen Variablen deklarieren, die lokal für diese Funktion sind. Die return-Anweisung kann verwendet werden, um jederzeit einen Wert zurückzugeben und die Funktion zu beenden. Wenn keine Rückgabe-Anweisung verwendet wird (oder eine leere Rückgabe ohne Wert), gibt JavaScript undefined zurück.

Funktionen können mit mehr oder weniger Parametern aufgerufen werden, als sie angeben. Wenn Sie eine Funktion ohne die Erwartung ihrer Parameter aufrufen, werden sie auf undefined gesetzt. Wenn Sie mehr Parameter übergeben, als erwartet, ignoriert die Funktion die zusätzlichen Parameter.

js
add(); // NaN
// Equivalent to add(undefined, undefined)

add(2, 3, 4); // 5
// added the first two; 4 was ignored

Es gibt eine Anzahl von anderen Parametersyntaxen. Zum Beispiel erlaubt die Restparameter-Syntax, alle vom Aufrufer übergebenen zusätzlichen Parameter in ein Array zu sammeln, ähnlich wie Pythons *args. (Da JS keine benannten Parameter auf Sprachebene hat, gibt es keine **kwargs.)

js
function avg(...args) {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
}

avg(2, 3, 4, 5); // 3.5

Im obigen Code enthalten die Variable args alle Werte, die an die Funktion übergeben wurden.

Der Restparameter speichert alle Argumente nach seiner Deklaration, aber nicht davor. Mit anderen Worten, function avg(firstValue, ...args) speichert den ersten Wert, der in die Funktion übergeben wurde, in der Variablen firstValue und die restlichen Argumente in args.

Wenn eine Funktion eine Liste von Argumenten akzeptiert und Sie sie bereits in einem Array halten, können Sie die Spread-Syntax im Funktionsaufruf verwenden, um das Array als Liste von Elementen zu erweitern. Zum Beispiel: avg(...numbers).

Wir erwähnten, dass JavaScript keine benannten Parameter hat. Es ist jedoch möglich, sie mithilfe des Objektdestructuring zu implementieren, das es Objekten ermöglicht, bequem gepackt und entpackt zu werden.

js
// Note the { } braces: this is destructuring an object
function area({ width, height }) {
  return width * height;
}

// The { } braces here create a new object
console.log(area({ width: 2, height: 3 }));

Es gibt auch die Standardparameter- Syntax, die es versäumten Parametern (oder denen, die als undefined übergeben werden) ermöglicht, einen Standardwert zu haben.

js
function avg(firstValue, secondValue, thirdValue = 0) {
  return (firstValue + secondValue + thirdValue) / 3;
}

avg(1, 2); // 1, instead of NaN

Anonyme Funktionen

JavaScript ermöglicht es Ihnen, anonyme Funktionen zu erstellen – das heißt, Funktionen ohne Namen. In der Praxis werden anonyme Funktionen typischerweise als Argumente für andere Funktionen verwendet, unmittelbar einer Variablen zugewiesen, die verwendet werden kann, um die Funktion aufzurufen, oder von einer anderen Funktion zurückgegeben.

js
// Note that there's no function name before the parentheses
const avg = function (...args) {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
};

Das macht die anonyme Funktion aufrufbar, indem avg() mit einigen Argumenten aufgerufen wird – das heißt, es ist semantisch äquivalent zur Deklaration der Funktion mit der function avg() {}-Deklarationssyntax.

Es gibt eine andere Möglichkeit, anonyme Funktionen zu definieren – mit einem Pfeilfunktionsausdruck.

js
// Note that there's no function name before the parentheses
const avg = (...args) => {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
};

// You can omit the `return` when simply returning an expression
const sum = (a, b, c) => a + b + c;

Pfeilfunktionen sind nicht semantisch äquivalent zu Funktionsausdrücken – für weitere Informationen siehe die Referenzseite.

Es gibt eine andere Möglichkeit, wie anonyme Funktionen nützlich sein können: Sie können gleichzeitig in einem einzigen Ausdruck deklariert und aufgerufen werden, dies wird Sofort aufgerufenem Funktionsausdruck (IIFE) genannt:

js
(function () {
  // …
})();

Für Anwendungsfälle von IIFEs können Sie über das Emulieren privater Methoden mit Closures lesen.

Rekursive Funktionen

JavaScript erlaubt es Ihnen, Funktionen rekursiv aufzurufen. Dies ist besonders nützlich für den Umgang mit Baumstrukturen, wie sie im DOM des Browsers zu finden sind.

js
function countChars(elm) {
  if (elm.nodeType === 3) {
    // TEXT_NODE
    return elm.nodeValue.length;
  }
  let count = 0;
  for (let i = 0, child; (child = elm.childNodes[i]); i++) {
    count += countChars(child);
  }
  return count;
}

Funktionsausdrücke können ebenfalls benannt sein, was es ihnen ermöglicht, rekursiv zu sein.

js
const charsInBody = (function counter(elm) {
  if (elm.nodeType === 3) {
    // TEXT_NODE
    return elm.nodeValue.length;
  }
  let count = 0;
  for (let i = 0, child; (child = elm.childNodes[i]); i++) {
    count += counter(child);
  }
  return count;
})(document.body);

Der im obigen Beispiel angegebene Name ist nur im eigenen Funktionsumfang der Funktion verfügbar. Dies ermöglicht dem Motor mehr Optimierungen und führt zu lesbarerem Code. Der Name wird auch im Debugger und einigen Stack-Traces angezeigt, was Ihnen beim Debugging Zeit sparen kann.

Wenn Sie funktionale Programmierung gewohnt sind, seien Sie sich der Leistungsimplikationen der Rekursion in JavaScript bewusst. Obwohl die Sprachspezifikation Tail-Call-Optimierung spezifiziert, wurde sie nur von JavaScriptCore (verwendet von Safari) implementiert, aufgrund der Schwierigkeit, Stack-Traces und Debugging wiederherzustellen. Für tiefe Rekursion sollten Sie in Betracht ziehen, Iteration zu verwenden, um Stapelüberläufe zu vermeiden.

Funktionen sind Objekte erster Klasse

JavaScript-Funktionen sind Objekte erster Klasse. Das bedeutet, dass sie Variablen zugewiesen, als Argumente an andere Funktionen übergeben und von anderen Funktionen zurückgegeben werden können. Zusätzlich unterstützt JavaScript Closures ohne explizites Erfassen, was es Ihnen ermöglicht, bequem funktionale Programmierstile anzuwenden.

js
// Function returning function
const add = (x) => (y) => x + y;
// Function accepting function
const babies = ["dog", "cat", "hen"].map((name) => `baby ${name}`);

Beachten Sie, dass JavaScript-Funktionen selbst Objekte sind – wie alles andere in JavaScript – und dass Sie ihnen wie früher in der Objektesektion gesehen, Eigenschaften hinzufügen oder ändern können.

Innere Funktionen

JavaScript-Funktionsdeklarationen sind auch innerhalb anderer Funktionen erlaubt. Ein wichtiges Detail von verschachtelten Funktionen in JavaScript ist, dass sie auf Variablen im Gültigkeitsbereich ihrer Elternfunktion zugreifen können:

js
function parentFunc() {
  const a = 1;

  function nestedFunc() {
    const b = 4; // parentFunc can't use this
    return a + b;
  }
  return nestedFunc(); // 5
}

Dies bietet eine große Menge an Nutzen beim Schreiben pflegbaren Codes. Wenn eine aufgerufene Funktion von einer oder zwei anderen Funktionen abhängt, die für keinen anderen Teil Ihres Codes nützlich sind, können Sie diese Hilfsfunktionen innerhalb davon schachteln. Dies hält die Anzahl der Funktionen, die im globalen Gültigkeitsbereich sind, niedrig.

Dies ist auch eine großartige Möglichkeit, dem Reiz globaler Variablen entgegenzuwirken. Wenn Sie komplexen Code schreiben, ist es oft verlockend, globale Variablen zu verwenden, um Werte zwischen mehreren Funktionen zu teilen, was zu schwer wartbarem Code führt. Verschachtelte Funktionen können Variablen in ihren Eltern teilen, sodass Sie diesen Mechanismus verwenden können, um Funktionen zusammen zu koppeln, ohne Ihren globalen Namensraum zu verschmutzen.

Klassen

JavaScript bietet die Klassen-Syntax, die sehr ähnlich zu Sprachen wie Java ist.

js
class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    return `Hello, I'm ${this.name}!`;
  }
}

const p = new Person("Maria");
console.log(p.sayHello());

JavaScript-Klassen sind nur Funktionen, die mit dem new-Operator instanziiert werden müssen. Jedes Mal, wenn eine Klasse instanziiert wird, gibt sie ein Objekt zurück, das die Methoden und Eigenschaften enthält, die die Klasse spezifiziert hat. Klassen erzwingen keine Codeorganisation – zum Beispiel können Sie Funktionen haben, die Klassen zurückgeben, oder Sie können mehrere Klassen pro Datei haben. Hier ist ein Beispiel dafür, wie ad hoc die Erstellung einer Klasse sein kann: Es ist nur ein Ausdruck, der von einer Pfeilfunktion zurückgegeben wird. Dieses Muster wird Mixin genannt.

js
const withAuthentication = (cls) =>
  class extends cls {
    authenticate() {
      // …
    }
  };

class Admin extends withAuthentication(Person) {
  // …
}

Statische Eigenschaften werden durch Voranstellen von static erstellt. Private Eigenschaften werden durch ein Hash # (nicht private) erstellt. Der Hash ist ein integraler Bestandteil des Eigenschaftennamens. (Denken Sie an # als _ in Python.) Im Gegensatz zu den meisten anderen Sprachen gibt es absolut keine Möglichkeit, eine private Eigenschaft außerhalb des Klassenkörpers zu lesen – auch nicht in abgeleiteten Klassen.

Für eine detaillierte Anleitung zu verschiedenen Klassenfunktionen können Sie die Leitfadenseite lesen.

Asynchrone Programmierung

JavaScript ist von Natur aus einspurig. Es gibt kein Parallelisieren; nur Nebenläufigkeit. Asynchrone Programmierung wird durch eine Ereignisschleife ermöglicht, die es ermöglicht, einen Satz von Aufgaben in die Warteschlange zu stellen und auf deren Abschluss zu prüfen.

Es gibt drei idiomatische Wege, um asynchronen Code in JavaScript zu schreiben:

Zum Beispiel könnte eine Datei-Lese-Operation in JavaScript so aussehen:

js
// Callback-based
fs.readFile(filename, (err, content) => {
  // This callback is invoked when the file is read, which could be after a while
  if (err) {
    throw err;
  }
  console.log(content);
});
// Code here will be executed while the file is waiting to be read

// Promise-based
fs.readFile(filename)
  .then((content) => {
    // What to do when the file is read
    console.log(content);
  })
  .catch((err) => {
    throw err;
  });
// Code here will be executed while the file is waiting to be read

// Async/await
async function readFile(filename) {
  const content = await fs.readFile(filename);
  console.log(content);
}

Die Kernsprache spezifiziert keine asynchronen Programmierfunktionen, aber es ist entscheidend bei der Interaktion mit der externen Umgebung – vom Anfragen von Benutzerberechtigungen, über das Einholen von Daten, bis hin zum Lesen von Dateien. Das Asynchrone Halten der potenziell langlaufenden Operationen stellt sicher, dass andere Prozesse dennoch laufen können, während dieser wartet – zum Beispiel wird der Browser nicht einfrieren, während er darauf wartet, dass der Benutzer auf eine Schaltfläche klickt, um die Erlaubnis zu erteilen.

Wenn Sie einen asynchronen Wert haben, ist es nicht möglich, seinen Wert synchron zu erhalten. Zum Beispiel, wenn Sie ein Promise haben, können Sie nur auf das endgültiges Ergebnis über die then()-Methode zugreifen. Ebenso kann await nur in einem asynchronen Kontext verwendet werden, der normalerweise eine async-Funktion oder ein Modul ist. Promises sind niemals blockierend – nur die Logik, die von dem Ergebnis des Promises abhängt, wird verschoben; alles andere wird weiterhin gleichzeitig ausgeführt. Wenn Sie ein funktionaler Programmierer sind, erkennen Sie Promises möglicherweise als Monaden, die mit then() abgebildet werden können (sie sind jedoch keine richtigen Monaden, weil sie sich automatisch abflachen; d.h. Sie können kein Promise<Promise<T>> haben).

Tatsächlich hat das Einfaden-Modell Node.js zu einer beliebten Wahl für serverseitige Programmierung gemacht, aufgrund seines nicht-blockierenden IO, das die Bearbeitung einer großen Anzahl von Datenbank- oder Dateisystemanfragen sehr leistungsfähig macht. CPU-gebundene (computational-intensive) Aufgaben, die reines JavaScript sind, blockieren jedoch immer noch den Haupt-Thread. Um echtes Parallelisieren zu erreichen, müssen Sie möglicherweise Workers verwenden.

Um mehr über asynchrone Programmierung zu erfahren, können Sie über das Verwenden von Promises lesen oder dem asynchronen JavaScript Tutorial folgen.

Module

JavaScript spezifiziert auch ein Modulsystem, das von den meisten Laufzeiten unterstützt wird. Ein Modul ist normalerweise eine Datei, die durch ihren Dateipfad oder URL identifiziert wird. Sie können die import und export Anweisungen verwenden, um Daten zwischen Modulen auszutauschen:

js
import { foo } from "./foo.js";

// Unexported variables are local to the module
const b = 2;

export const a = 1;

Im Gegensatz zu Haskell, Python, Java, etc. wird die Modulauflösung von JavaScript vollständig hostdefiniert – sie basiert normalerweise auf URLs oder Dateipfaden, sodass relative Dateipfade "einfach funktionieren" und relativ zum Pfad des aktuellen Moduls, anstatt zu einem Projektstammpfad, sind.

Allerdings bietet die JavaScript-Sprache keine Standardbibliotheksmodule – alle Kernfunktionen werden durch globale Variablen wie Math und Intl bereitgestellt. Dies ist darauf zurückzuführen, dass JavaScript lange Zeit ohne Modulsystem auskam und die Tatsache, dass die Teilnahme am Modulsystem einige Änderungen an der Runtime-Einrichtung erfordert.

Unterschiedliche Laufzeiten können unterschiedliche Modulsysteme verwenden. Zum Beispiel verwendet Node.js den Paketmanager npm und ist größtenteils dateisystembasiert, während Deno und Browser vollständig URL-basiert sind, und Module aus HTTP-URLs aufgelöst werden können.

Für weitere Informationen siehe die Module-Leitfadenseite.

Sprache und Laufzeit

Auf dieser Seite haben wir ständig erwähnt, dass bestimmte Funktionen sprachebene sind, während andere laufzeitebene sind.

JavaScript ist eine Allzweckscript-Sprache. Die Core-Sprachspezifikation konzentriert sich auf reine Berechnungslogik. Sie befasst sich nicht mit Ein-/Ausgabe – ohne zusätzliche laufzeitebene APIs (insbesondere console.log()), ist das Verhalten eines JavaScript-Programms vollständig unbeobachtbar.

Eine Laufzeit, oder ein Host, ist etwas, das dem JavaScript-Engine (dem Interpreter) Daten zuführt, zusätzliche globale Eigenschaften bereitstellt und Hooks für den Engine bereitstellt, um mit der Außenwelt zu interagieren. Modulauflösung, das Lesen von Daten, das Drucken von Nachrichten, das Senden von Netzwerkanfragen, etc. sind alles laufzeitebene Operationen. Seit seiner Einführung wurde JavaScript in verschiedenen Umgebungen übernommen, wie z.B. Browsern (die APIs wie DOM bereitstellen), Node.js (das APIs wie Dateisystemzugriff bereitstellt), etc. JavaScript wurde erfolgreich in das Web integriert (was sein Hauptzweck war), Mobile Apps, Desktop Apps, serverseitige Apps, Serverless, eingebettete Systeme und mehr. Während Sie über JavaScript-Kernfunktionen lernen, ist es auch wichtig, hostbereitgestellte Funktionen zu verstehen, um das Wissen anzuwenden. Zum Beispiel können Sie über alle Webplattform-APIs lesen, die von Browsern und manchmal auch Nicht-Browsern implementiert werden.

Weitere Erkundung

Diese Seite bietet einen sehr grundlegenden Einblick, wie verschiedene JavaScript-Funktionen mit anderen Sprachen verglichen werden. Wenn Sie mehr über die Sprache selbst und die Nuancen jeder Funktion erfahren möchten, können Sie den JavaScript-Leitfaden und die JavaScript-Referenz lesen.

Es gibt einige wesentliche Teile der Sprache, die wir aufgrund von Platz und Komplexität ausgelassen haben, die Sie selbst erkunden können: