Skip to the content.

Modern JavaScript Cheatsheet

Modern JavaScript cheatsheet Bildnachweis: Ahmad Awais ⚡️

Wenn dir dieser Inhalt gefällt, kannst du mich anpingen oder mir auf Twitter folgen :+1:

Tweet for help

Einführung

Motivation

Dieses Dokument ist ein Spickzettel für JavaScript, das du häufig in modernen Projekten und den meisten aktuellen Beispielcodes antreffen wirst.

Dieser Leitfaden ist nicht dazu gedacht, dir JavaScript von Grund auf beizubringen, sondern ist eine Hilfe für Entwickler mit grundlegendem Wissen, die Schwierigkeiten haben, sich mit modernen Codebasen vertraut zu machen (oder sagen wir, React zu lernen), aufgrund der in JavaScript verwendeten Konzepte.

Außerdem werde ich manchmal persönliche Tipps geben, die diskutierbar sein könnten, aber ich werde darauf hinweisen, dass es eine persönliche Empfehlung ist, wenn ich das tue.

Hinweis: Die meisten der hier eingeführten Konzepte stammen aus einem JavaScript Sprachupdate (ES2015, oft als ES6 bezeichnet). Neue Funktionen, die durch dieses Update hinzugefügt wurden, findest du hier; sehr gut gemacht.

Ergänzende Ressourcen

Wenn du Schwierigkeiten hast, eine Vorstellung zu verstehen, schlage ich vor, dass du nach Antworten in den folgenden Ressourcen suchst:

Inhaltsverzeichnis

Begriffe

Variablendeklaration: var, const, let

In JavaScript gibt es drei Schlüsselwörter, um eine Variable zu deklarieren, und jedes hat seine Unterschiede. Diese Schlüsselwörter sind var, let und const.

Kurze Erklärung

Variablen, die mit dem Schlüsselwort const deklariert sind, können nicht neu zugewiesen werden, während let und var neu zugewiesen werden können.

Ich empfehle, deine Variablen standardmäßig mit const zu deklarieren, aber mit let, wenn es eine Variable ist, die du später mutieren oder neu zuweisen musst.

Scope Neu zuzuweisend Veränderbar Temporal Dead Zone
const Block Nein Ja Ja
let Block Ja Ja Ja
var Funktion Ja Ja Nein

Beispielcode

const person = "Nick";
person = "John" // Wird einen Fehler werfen, person kann nicht neu zugewiesen werden
let person = "Nick";
person = "John";
console.log(person) // "John", Neu-Zuweisung ist erlaubt mit let

Ausführliche Erklärung

Der Scope einer Variable bedeutet grob “wo ist diese Variable im Code verfügbar”.

var

var deklarierte Variablen sind funktionsumfänglich, was bedeutet, dass, wenn eine Variable in einer Funktion erstellt wird, alles in dieser Funktion Zugriff auf diese Variable hat. Außerdem kann eine funktionsumfängliche Variable, die in einer Funktion erstellt wurde, nicht außerhalb dieser Funktion zugegriffen werden.

Ich empfehle dir, es dir so vorzustellen, als ob eine X-umfängliche Variable bedeutet, dass diese Variable eine Eigenschaft von X ist.

function myFunction() {
  var myVar = "Nick";
  console.log(myVar); // "Nick" - myVar ist innerhalb der Funktion zugänglich
}
console.log(myVar); // Wirft einen ReferenceError, myVar ist außerhalb der Funktion nicht zugänglich.

Fokussieren wir uns immer noch auf den Variablenscope, hier ist ein subtileres Beispiel:

function myFunction() {
  var myVar = "Nick";
  if (true) {
    var myVar = "John";
    console.log(myVar); // "John"
    // eigentlich, da myVar funktionsumfänglich ist, haben wir einfach den vorherigen myVar-Wert "Nick" durch "John" ersetzt
  }
  console.log(myVar); // "John" - sieh, wie die Anweisungen im if-Block diesen Wert beeinflusst haben
}
console.log(myVar); // Wirft einen ReferenceError, myVar ist außerhalb der Funktion nicht zugänglich.

Außerdem werden var deklarierte Variablen zum Anfang des Scopes bei der Ausführung verschoben. Dies nennt man var hoisting.

Dieser Codeabschnitt:

console.log(myVar) // undefined -- kein Fehler geworfen
var myVar = 2;

wird bei der Ausführung so verstanden:

var myVar;
console.log(myVar) // undefined -- kein Fehler geworfen
myVar = 2;
let

var und let sind ungefähr gleich, aber bei let deklarierte Variablen

Lasst uns die Auswirkung der Block-Scoping anhand unseres vorherigen Beispiels sehen:

function myFunction() {
  let myVar = "Nick";
  if (true) {
    let myVar = "John";
    console.log(myVar); // "John"
    // eigentlich, da myVar einen Block-Scope hat, haben wir gerade eine neue Variable myVar erstellt.
    // Diese Variable ist außerhalb dieses Blocks nicht zugänglich und völlig unabhängig
    // von der ersten erstellten myVar!
  }
  console.log(myVar); // "Nick", sieh, wie die Anweisungen im if-Block diesen Wert NICHT beeinflusst haben
}
console.log(myVar); // Wirft einen ReferenceError, myVar ist außerhalb der Funktion nicht zugänglich.

Nun, was es für let (und const) Variablen bedeutet, nicht zugänglich zu sein, bevor sie zugewiesen werden:

console.log(myVar) // wirft einen ReferenceError !
let myVar = 2;

Im Gegensatz zu var Variablen, wenn du versuchst, eine let oder const Variable zu lesen oder zu schreiben, bevor sie zugewiesen sind, wird ein Fehler ausgelöst. Dieses Phänomen wird oft Temporal Dead Zone oder TDZ genannt.

Hinweis: Technisch gesehen werden let und const Variablendeklarationen auch gehoistet, aber nicht ihre Zuweisung. Da sie so gemacht sind, dass sie nicht vor der Zuweisung verwendet werden können, fühlt es sich intuitiv an, als ob es kein Hoisting gäbe, aber es gibt es. Finde mehr in dieser sehr detaillierten Erklärung heraus, wenn du mehr wissen möchtest.

Außerdem kannst du eine let Variable nicht neu deklarieren:

let myVar = 2;
let myVar = 3; // Wirft einen SyntaxError
const

Mit const deklarierte Variablen verhalten sich wie let-Variablen, können aber auch nicht neu zugewiesen werden.

Zusammenfassend können const-Variablen:

const myVar = "Nick";
myVar = "John" // Wirft einen Fehler, Neuzuweisung ist nicht erlaubt
const myVar = "Nick";
const myVar = "John" // Wirft einen Fehler, Neudeklarierung ist nicht erlaubt

Aber es gibt eine Feinheit: const Variablen sind nicht unveränderbar! Konkret bedeutet das, dass Objekt- und Array-const deklarierte Variablen verändert werden können.

Für Objekte:

const person = {
  name: 'Nick'
};
person.name = 'John' // das funktioniert! Die Variable person wird nicht komplett neu zugewiesen, sondern verändert
console.log(person.name) // "John"
person = "Sandra" // wirft einen Fehler, da Neu-Zuweisung nicht erlaubt ist für const deklarierte Variablen

Für Arrays:

const person = [];
person.push('John'); // das funktioniert! Die Variable person wird nicht komplett neu zugewiesen, sondern verändert
console.log(person[0]) // "John"
person = ["Nick"] // wirft einen Fehler, da Neu-Zuweisung nicht erlaubt ist für const deklarierte Variablen

Externe Ressource

Pfeilfunktion

Das ES6 JavaScript Update hat Pfeilfunktionen eingeführt, die eine weitere Methode darstellen, Funktionen zu deklarieren und zu verwenden. Hier sind die Vorteile, die sie mitbringen:

Beispielcode

function double(x) { return x * 2; } // Traditioneller Weg
console.log(double(2)) // 4
const double = x => x * 2; // Gleiche Funktion als Pfeilfunktion mit impliziter Rückgabe geschrieben
console.log(double(2)) // 4

In einer Pfeilfunktion ist this gleich dem this-Wert des umschließenden Ausführungskontextes. Im Grunde musst du mit Pfeilfunktionen nicht mehr den Trick “that = this” anwenden, bevor du eine Funktion in einer Funktion aufrufst.

function myFunc() {
  this.myVar = 0;
  setTimeout(() => {
    this.myVar++;
    console.log(this.myVar) // 1
  }, 0);
}

Ausführliche Erklärung

Prägnanz

Pfeilfunktionen sind in vielerlei Hinsicht prägnanter als traditionelle Funktionen. Lass uns alle möglichen Fälle überprüfen:

Eine explizite Rückgabe ist eine Funktion, in deren Körper das Schlüsselwort return verwendet wird.

  function double(x) {
    return x * 2; // diese Funktion gibt explizit x * 2 zurück, das Schlüsselwort *return* wird verwendet
  }

Auf traditionelle Weise geschriebene Funktionen hatten immer eine explizite Rückgabe. Aber mit Pfeilfunktionen kannst du eine implizite Rückgabe durchführen, was bedeutet, dass du das Schlüsselwort return nicht verwenden musst, um einen Wert zurückzugeben.

  const double = (x) => {
    return x * 2; // Hier explizite Rückgabe
  }

Da diese Funktion nur etwas zurückgibt (keine Anweisungen vor dem Schlüsselwort return), können wir eine implizite Rückgabe durchführen.

  const double = (x) => x * 2; // Korrekt, gibt x*2 zurück

Um dies zu tun, müssen wir nur die Klammern und das Schlüsselwort return entfernen. Daher wird sie als implizite Rückgabe bezeichnet, das Schlüsselwort return ist nicht vorhanden, aber diese Funktion wird tatsächlich x * 2 zurückgeben.

Hinweis: Wenn deine Funktion keinen Wert zurückgibt (mit Nebenwirkungen), führt sie weder eine explizite noch eine implizite Rückgabe durch.

Außerdem, wenn du ein Objekt implizit zurückgeben möchtest, musst du es in Klammern setzen, da es sonst zu Konflikten mit den Blockklammern kommen würde:

const getPerson = () => ({ name: "Nick", age: 24 })
console.log(getPerson()) // { name: "Nick", age: 24 } -- Objekt wird implizit durch Pfeilfunktion zurückgegeben

Wenn deine Funktion nur einen Parameter hat, kannst du die Klammern um ihn herum weglassen. Wenn wir den oben genannten double-Code wieder aufgreifen:

  const double = (x) => x * 2; // diese Pfeilfunktion nimmt nur einen Parameter entgegen

Klammern um den Parameter können vermieden werden:

  const double = x => x * 2; // diese Pfeilfunktion nimmt nur einen Parameter entgegen

Wenn einer Pfeilfunktion kein Argument übergeben wird, musst du Klammern angeben, sonst ist die Syntax nicht gültig.

  () => { // Klammern werden angegeben, alles ist in Ordnung
    const x = 2;
    return x;
  }
  => { // Keine Klammern, das wird nicht funktionieren!
    const x = 2;
    return x;
  }
this-Referenz

Um diese Nuance, die mit Pfeilfunktionen eingeführt wurde, zu verstehen, muss du wissen, wie this sich in JavaScript verhält.

In einer Pfeilfunktion ist this gleich dem this-Wert des umschließenden Ausführungskontextes. Das bedeutet, dass eine Pfeilfunktion kein neues this erstellt, sondern es stattdessen aus seiner Umgebung übernimmt.

Ohne Pfeilfunktion, wenn du in einer Funktion innerhalb einer Funktion auf eine Variable von this zugreifen wolltest, musstest du den Trick that = this oder self = this anwenden.

Zum Beispiel beim Verwenden der setTimeout-Funktion innerhalb von myFunc:

function myFunc() {
  this.myVar = 0;
  var that = this; // that = this Trick
  setTimeout(
    function() { // Ein neues *this* wird in diesem Funktionsbereich erstellt
      that.myVar++;
      console.log(that.myVar) // 1

      console.log(this.myVar) // undefiniert -- siehe Funktionsdeklaration oben
    },
    0
  );
}

Aber mit Pfeilfunktion wird this aus seiner Umgebung übernommen:

function myFunc() {
  this.myVar = 0;
  setTimeout(
    () => { // this wird aus der Umgebung übernommen, was hier myFunc bedeutet
      this.myVar++;
      console.log(this.myVar) // 1
    },
    0
  );
}

Nützliche Ressourcen

Standardparameterwert für Funktionen

Ab dem ES2015 JavaScript Update kannst du deinen Funktionsparametern Standardwerte zuweisen, indem du folgende Syntax verwendest:

function myFunc(x = 10) {
  return x;
}
console.log(myFunc()) // 10 -- kein Wert wird übergeben, daher wird dem x in myFunc der Standardwert 10 zugewiesen
console.log(myFunc(5)) // 5 -- ein Wert wird übergeben, daher ist x gleich 5 in myFunc

console.log(myFunc(undefined)) // 10 -- der Wert undefined wird übergeben, daher wird der Standardwert dem x zugewiesen
console.log(myFunc(null)) // null -- ein Wert (null) wird übergeben, siehe unten für weitere Details

Der Standardparameter wird in zwei und nur zwei Situationen angewendet:

Anders ausgedrückt, wenn du null übergibst, wird der Standardparameter nicht angewendet.

Hinweis: Die Zuweisung eines Standardwerts kann auch mit destrukturierten Parametern verwendet werden (siehe nächste Notion, um ein Beispiel zu sehen)

Externe Ressource

Destrukturierung von Objekten und Arrays

Destrukturierung ist eine praktische Methode, neue Variablen zu erstellen, indem einige Werte aus in Objekten oder Arrays gespeicherten Daten extrahiert werden.

Um nur einige Anwendungsfälle zu nennen, kann die Destrukturierung verwendet werden, um Funktionsparameter oder this.props in React-Projekten zu destrukturieren.

Erklärung mit Beispielcode

Betrachten wir das folgende Objekt für alle Beispiele:

const person = {
  firstName: "Nick",
  lastName: "Anderson",
  age: 35,
  sex: "M"
}

Ohne Destrukturierung

const first = person.firstName;
const age = person.age;
const city = person.city || "Paris";

Mit Destrukturierung, alles in einer Zeile:

const { firstName: first, age, city = "Paris" } = person; // Das war's !

console.log(age) // 35 -- Eine neue Variable age wird erstellt und ist gleich person.age
console.log(first) // "Nick" -- Eine neue Variable first wird erstellt und ist gleich person.firstName
console.log(firstName) // ReferenceError -- person.firstName existiert, ABER die neu erstellte Variable ist als first benannt
console.log(city) // "Paris" -- Eine neue Variable city wird erstellt und da person.city undefiniert ist, ist city gleich dem bereitgestellten Standardwert "Paris".

Hinweis: In const { age } = person; werden die Klammern nach dem const-Schlüsselwort nicht verwendet, um ein Objekt oder einen Block zu deklarieren, sondern es ist die Destrukturierungssyntax.

Destrukturierung wird häufig verwendet, um Objektparameter in Funktionen zu destrukturieren.

Ohne Destrukturierung

function joinFirstLastName(person) {
  const firstName = person.firstName;
  const lastName = person.lastName;
  return firstName + '-' + lastName;
}

joinFirstLastName(person); // "Nick-Anderson"

Bei der Destrukturierung des Objektparameters person erhalten wir eine präzisere Funktion:

function joinFirstLastName({ firstName, lastName }) { // wir erstellen Variablen firstName und lastName durch Destrukturierung des person-Parameters
  return firstName + '-' + lastName;
}

joinFirstLastName(person); // "Nick-Anderson"

Destrukturierung ist noch angenehmer mit Pfeilfunktionen zu verwenden:

const joinFirstLastName = ({ firstName, lastName }) => firstName + '-' + lastName;

joinFirstLastName(person); // "Nick-Anderson"

Betrachten wir das folgende Array:

const myArray = ["a", "b", "c"];

Ohne Destrukturierung

const x = myArray[0];
const y = myArray[1];

Mit Destrukturierung

const [x, y] = myArray; // Das war's !

console.log(x) // "a"
console.log(y) // "b"

Nützliche Ressourcen

Array-Methoden - map / filter / reduce / find

Map, filter, reduce und find sind Array-Methoden, die aus einem Programmierparadigma namens funktionale Programmierung stammen.

Zusammengefasst:

Ich empfehle, sie so oft wie möglich zu verwenden, indem du den Prinzipien der funktionalen Programmierung folgst, da sie zusammensetzbar, prägnant und elegant sind.

Mit diesen vier Methoden kannst du in den meisten Situationen die Verwendung von for und forEach Schleifen vermeiden. Wenn du versucht bist, eine for-Schleife zu machen, versuche es mit map, filter, reduce und find zusammengesetzt. Es könnte anfangs schwierig sein, da es dich zwingt, eine neue Denkweise zu erlernen, aber sobald du es beherrschst, wird alles einfacher.

Beispielcode

const numbers = [0, 1, 2, 3, 4, 5, 6];
const doubledNumbers = numbers.map(n => n * 2); // [0, 2, 4, 6, 8, 10, 12]
const evenNumbers = numbers.filter(n => n % 2 === 0); // [0, 2, 4, 6]
const sum = numbers.reduce((prev, next) => prev + next, 0); // 21
const greaterThanFour = numbers.find((n) => n>4); // 5

Berechne die Gesamtsumme der Noten für Schüler mit Noten 10 oder darüber, indem du map, filter und reduce zusammensetzt:

const students = [
  { name: "Nick", grade: 10 },
  { name: "John", grade: 15 },
  { name: "Julia", grade: 19 },
  { name: "Nathalie", grade: 9 },
];

const aboveTenSum = students
  .map(student => student.grade) // wir mappen das students Array auf ein Array ihrer Noten
  .filter(grade => grade >= 10) // wir filtern das Noten-Array, um diejenigen 10 oder darüber zu behalten
  .reduce((prev, next) => prev + next, 0); // wir summieren alle Noten 10 oder darüber nacheinander

console.log(aboveTenSum) // 44 -- 10 (Nick) + 15 (John) + 19 (Julia), Nathalie unter 10 wird ignoriert

Erklärung

Betrachten wir das folgende Array von Zahlen für unsere Beispiele:

const numbers = [0, 1, 2, 3, 4, 5, 6];
Array.prototype.map()
const doubledNumbers = numbers.map(function(n) {
  return n * 2;
});
console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12]

Was passiert hier? Wir verwenden .map auf dem numbers-Array, die Map iteriert über jedes Element des Arrays und übergibt es unserer Funktion. Das Ziel der Funktion ist es, einen neuen Wert aus dem übergebenen Wert zu erzeugen und zurückzugeben, so dass map ihn ersetzen kann.

Lasst uns diese Funktion extrahieren, um es einmal klarer zu machen:

const doubleN = function(n) { return n * 2; };
const doubledNumbers = numbers.map(doubleN);
console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12]

Hinweis: Du wirst diese Methode oft in Kombination mit Pfeilfunktionen antreffen

const doubledNumbers = numbers.map(n => n * 2);
console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12]

numbers.map(doubleN) produziert [doubleN(0), doubleN(1), doubleN(2), doubleN(3), doubleN(4), doubleN(5), doubleN(6)], was gleich [0, 2, 4, 6, 8, 10, 12] ist.

Hinweis: Wenn du kein neues Array zurückgeben musst und nur eine Schleife mit Nebeneffekten machen willst, solltest du vielleicht stattdessen eine for / forEach-Schleife anstelle von map verwenden.

Array.prototype.filter()
const evenNumbers = numbers.filter(function(n) {
  return n % 2 === 0; // true, wenn "n" gerade ist, false, wenn "n" ungerade ist
});
console.log(evenNumbers); // [0, 2, 4, 6]

Hinweis: Du wirst diese Methode oft in Kombination mit Pfeilfunktionen antreffen

const evenNumbers = numbers.filter(n => n % 2 === 0);
console.log(evenNumbers); // [0, 2, 4, 6]

Wir verwenden .filter auf dem numbers-Array, filter iteriert über jedes Element des Arrays und übergibt es unserer Funktion. Das Ziel der Funktion ist es, einen Boolean zurückzugeben, der bestimmt, ob der aktuelle Wert behalten wird oder nicht. Filter gibt dann das Array nur mit den behaltenen Werten zurück.

Array.prototype.reduce()

Das Ziel der reduce-Methode ist es, alle Elemente des Arrays, über das sie iteriert, in einen einzigen Wert zu reduzieren. Wie diese Elemente aggregiert werden, liegt bei dir.

const sum = numbers.reduce(
  function(acc, n) {
    return acc + n;
  },
  0 // Wert der Akkumulatorvariablen beim ersten Iterationsschritt
);

console.log(sum) // 21

Hinweis: Du wirst diese Methode oft in Kombination mit Pfeilfunktionen antreffen

const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum) // 21

Wie bei den .map- und .filter-Methoden wird .reduce auf einem Array angewandt und nimmt eine Funktion als ersten Parameter.

Diesmal gibt es allerdings Änderungen:

Der erste Parameter ist eine Funktion, die bei jedem Iterationsschritt aufgerufen wird.

Der zweite Parameter ist der Wert der Akkumulatorvariablen (acc hier) beim ersten Iterationsschritt (lies den nächsten Punkt, um dies zu verstehen).

Die Funktion, die du als ersten Parameter von .reduce übergibst, nimmt zwei Parameter entgegen. Der erste (acc hier) ist die Akkumulatorvariable, während der zweite Parameter (n) das aktuelle Element ist.

Die Akkumulatorvariable entspricht dem Rückgabewert deiner Funktion im vorherigen Iterationsschritt. Beim ersten Schritt der Iteration ist acc gleich dem Wert, den du als zweiten Parameter von .reduce übergeben hast.

Beim ersten Iterationsschritt

acc = 0, weil wir 0 als zweiten Parameter für reduce übergeben haben

n = 0, erstes Element des numbers-Arrays

Die Funktion gibt acc + n –> 0 + 0 –> 0 zurück

Beim zweiten Iterationsschritt

acc = 0, weil es der Wert ist, den die Funktion im vorherigen Iterationsschritt zurückgegeben hat

n = 1, zweites Element des numbers-Arrays

Die Funktion gibt acc + n –> 0 + 1 –> 1 zurück

Beim dritten Iterationsschritt

acc = 1, weil es der Wert ist, den die Funktion im vorherigen Iterationsschritt zurückgegeben hat

n = 2, drittes Element des numbers-Arrays

Die Funktion gibt acc + n –> 1 + 2 –> 3 zurück

Beim vierten Iterationsschritt

acc = 3, weil es der Wert ist, den die Funktion im vorherigen Iterationsschritt zurückgegeben hat

n = 3, viertes Element des numbers-Arrays

Die Funktion gibt acc + n –> 3 + 3 –> 6 zurück

[…] Beim letzten Iterationsschritt

acc = 15, weil es der Wert ist, den die Funktion im vorherigen Iterationsschritt zurückgegeben hat

n = 6, letztes Element des numbers-Arrays

Die Funktion gibt acc + n –> 15 + 6 –> 21 zurück

Da dies der letzte Iterationsschritt ist, gibt .reduce 21 zurück.

Array.prototype.find()
const greaterThanZero = numbers.find(function(n) {
  return n > 0; // gibt die Nummer zurück, die gerade größer als 0 ist
});
console.log(greaterThanZero); // 1

Hinweis: Du wirst diese Methode oft in Kombination mit Pfeilfunktionen antreffen

Wir verwenden .find auf dem numbers-Array, .find iteriert über jedes Element des Arrays und übergibt es unserer Funktion, bis die Bedingung erfüllt ist. Das Ziel der Funktion ist es, das Element zurückzugeben, das die aktuelle Testfunktion erfüllt. Die .find-Methode führt die Callback-Funktion einmal für jeden Index des Arrays aus, bis der Callback einen wahren Wert zurückgibt.

Hinweis: Sie gibt sofort den Wert des Elements zurück (das die Bedingung erfüllt), wenn gefunden. Andernfalls gibt sie undefined zurück.

Externe Ressource

Spread-Operator “…”

Der Spread-Operator ... wurde mit ES2015 eingeführt und wird verwendet, um Elemente eines iterierbaren Objekts (wie ein Array) an Stellen auszubreiten, an denen mehrere Elemente passen können.

Beispielcode

const arr1 = ["a", "b", "c"];
const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"]
function myFunc(x, y, ...params) {
  console.log(x);
  console.log(y);
  console.log(params)
}

myFunc("a", "b", "c", "d", "e", "f")
// "a"
// "b"
// ["c", "d", "e", "f"]
const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }

const n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }

Erklärung

In Iterables (wie Arrays)

Wenn wir die folgenden zwei Arrays haben:

const arr1 = ["a", "b", "c"];
const arr2 = [arr1, "d", "e", "f"]; // [["a", "b", "c"], "d", "e", "f"]

arr2 das erste Element ist ein Array, weil arr1 so, wie es ist, in arr2 eingefügt wird. Aber was wir wollen, ist, dass arr2 ein Array von Buchstaben ist. Um dies zu erreichen, können wir die Elemente von arr1 in arr2 ausbreiten.

Mit dem Spread-Operator

const arr1 = ["a", "b", "c"];
const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"]
Funktionsrestparameter

In Funktionsparametern können wir den Rest-Operator verwenden, um Parameter in einem Array einzufügen, über das wir iterieren können. Es gibt bereits ein arguments-Objekt, das an jede Funktion gebunden ist und einem Array mit allen Parametern entspricht, die in die Funktion eingegeben wurden.

function myFunc() {
  for (var i = 0; i < arguments.length; i++) {
    console.log(arguments[i]);
  }
}

myFunc("Nick", "Anderson", 10, 12, 6);
// "Nick"
// "Anderson"
// 10
// 12
// 6

Aber nehmen wir an, dass wir wollen, dass diese Funktion einen neuen Schüler mit seinen Noten und seinem Durchschnitt erstellt. Wäre es nicht praktischer, die ersten beiden Parameter in zwei separate Variablen zu extrahieren und dann alle Noten in einem Array zu haben, über das wir iterieren können?

Genau das ermöglicht der Rest-Operator!

function createStudent(firstName, lastName, ...grades) {
  // firstName = "Nick"
  // lastName = "Anderson"
  // [10, 12, 6] -- "..." nimmt alle anderen übergebenen Parameter und erzeugt eine "grades"-Arrayvariable, die sie enthält

  const avgGrade = grades.reduce((acc, curr) => acc + curr, 0) / grades.length; // berechnet den Durchschnitt aus den Noten

  return {
    firstName: firstName,
    lastName: lastName,
    grades: grades,
    avgGrade: avgGrade
  }
}

const student = createStudent("Nick", "Anderson", 10, 12, 6);
console.log(student);
// {
//   firstName: "Nick",
//   lastName: "Anderson",
//   grades: [10, 12, 6],
//   avgGrade: 9,33
// }

Hinweis: Die Funktion createStudent ist schlecht, weil wir nicht überprüfen, ob grades.length existiert oder verschieden von 0 ist. Aber so ist es einfacher zu lesen, deshalb habe ich diesen Fall nicht behandelt.

Objekteigenschaften ausbreiten

Für dieses Beispiel empfehle ich, die vorherigen Erklärungen zum Rest-Operator bei iterierbaren Objekten und Funktionsparametern zu lesen.

const myObj = { x: 1, y: 2, a: 3, b: 4 };
const { x, y, ...z } = myObj; // Objektdestrukturierung hier
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }

// z sind die restlichen Eigenschaften des destrukturierten Objekts: myObj-Objekt minus x- und y-Eigenschaften, die destrukturiert wurden

const n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }

// Hier werden die Eigenschaften von z-Objekten in n ausgespreitet

Externe Ressourcen

Objekteigenschaften Kurzschreibweise

Wenn man einer Objekteigenschaft eine Variable zuweist, deren Name gleich dem Eigenschaftsnamen ist, kann man folgendes tun:

const x = 10;
const myObj = { x };
console.log(myObj.x) // 10

Erklärung

Normalerweise (vor ES2015) wenn du ein neues Objektliteral deklarierst und Variablen als Werte für Objekteigenschaften verwenden möchtest, würdest du diesen Code schreiben:

const x = 10;
const y = 20;

const myObj = {
  x: x, // weist x Variable Wert myObj.x zu
  y: y // weist y Variable Wert myObj.y zu
};

console.log(myObj.x) // 10
console.log(myObj.y) // 20

Wie du sehen kannst, ist das ziemlich wiederholend, weil die Namen der Eigenschaften von myObj den Variablennamen entsprechen, die du diesen zuweisen willst.

Mit ES2015 kannst du diese Abkürzung verwenden, wenn der Variablenname dem Namen der Eigenschaft entspricht:

const x = 10;
const y = 20;

const myObj = {
  x,
  y
};

console.log(myObj.x) // 10
console.log(myObj.y) // 20

Externe Ressourcen

Promises

Ein Promise ist ein Objekt, das synchron aus einer asynchronen Funktion zurückgegeben werden kann (ref).

Promises können verwendet werden, um der Callback-Hölle zu entkommen, und sie begegnen uns in modernen JavaScript-Projekten immer häufiger.

Beispielcode

const fetchingPosts = new Promise((res, rej) => {
  $.get("/posts")
    .done(posts => res(posts))
    .fail(err => rej(err));
});

fetchingPosts
  .then(posts => console.log(posts))
  .catch(err => console.log(err));

Erklärung

Wenn du eine Ajax-Anfrage machst, ist die Antwort nicht synchron, weil du eine Ressource anforderst, die einige Zeit zur Antwort braucht. Sie könnte sogar nie kommen, falls die angeforderte Ressource aus irgendeinem Grund nicht verfügbar ist (404).

Um diese Art von Situation zu handhaben, hat uns ES2015 Promises gegeben. Promises können drei verschiedene Zustände haben:

Nehmen wir an, wir möchten Promises verwenden, um eine Ajax-Anfrage zu tätigen und die Ressource X zu holen.

Das Promise erstellen

Zuerst erstellen wir ein Promise. Wir verwenden die jQuery get-Methode, um unsere Ajax-Anfrage an X zu stellen.

const xFetcherPromise = new Promise( // Erstelle Promise mit dem "new" Schlüsselwort und speichere es in einer Variable
  function(resolve, reject) { // Der Promise-Konstruktor nimmt eine Funktion entgegen, die selbst die Parameter resolve und reject besitzt
    $.get("X") // Starte die Ajax-Anfrage
      .done(function(X) { // Sobald die Anfrage beendet ist...
        resolve(X); // ... erfülle das Promise mit dem Wert X als Parameter
      })
      .fail(function(error) { // Falls die Anfrage fehlschlägt...
        reject(error); // ... lehne das Promise mit dem Fehler als Parameter ab
      });
  }
)

Wie im obigen Beispiel zu sehen ist, nimmt das Promise-Objekt eine Executor-Funktion entgegen, die zwei Parameter resolve und reject hat. Diese Parameter sind Funktionen, die, wenn aufgerufen, den Promise-Zustand pending jeweils in einen fulfilled und rejected Zustand überführen.

Das Promise befindet sich in einem Pending-Zustand nach seiner Erstellung, und seine Executor-Funktion wird sofort ausgeführt. Sobald eine der Funktionen resolve oder reject in der Executor-Funktion aufgerufen wird, ruft das Promise seine zugehörigen Handler auf.

Verwendung von Promise-Handlern

Um das Ergebnis (oder den Fehler) des Promises zu erhalten, müssen wir ihm Handler zuweisen, indem wir Folgendes tun:

xFetcherPromise
  .then(function(X) {
    console.log(X);
  })
  .catch(function(err) {
    console.log(err)
  })

Wenn das Promise erfolgreich ist, wird resolve ausgeführt und die Funktion, die als .then Parameter übergeben wurde, wird ausgeführt.

Wenn es fehlschlägt, wird reject ausgeführt und die Funktion, die als .catch Parameter übergeben wurde, wird ausgeführt.

Hinweis : Wenn das Promise bereits erfüllt oder abgelehnt wurde, wenn ein entsprechender Handler angehängt wird, wird der Handler aufgerufen, sodass es keinen Wettlaufzustand zwischen dem Abschluss einer asynchronen Operation und dem Anhängen ihrer Handler gibt. (Ref: MDN)

Externe Ressourcen

Template Literale

Template Literale sind eine Ausdrucksinterpolation für ein- und mehrzeilige Strings.

Anders ausgedrückt, handelt es sich um eine neue String-Syntax, in der du bequem jede JavaScript-Ausdrücke verwenden kannst (beispielsweise Variablen).

Beispielcode

const name = "Nick";
`Hallo ${name}, der folgende Ausdruck ist gleich vier : ${2+2}`;

// Hallo Nick, der folgende Ausdruck ist gleich vier: 4

Externe Ressourcen

Tagged Template Literale

Template Tags sind Funktionen, die einem Template Literal vorangestellt werden können. Wenn eine Funktion auf diese Weise aufgerufen wird, ist der erste Parameter ein Array der Strings, die zwischen den interpolierten Variablen des Templates auftreten, und die nachfolgenden Parameter sind die interpolierten Werte. Verwende einen Spread-Operator ..., um alle zu erfassen. (Ref: MDN).

Hinweis : Eine berühmte Bibliothek namens styled-components setzt schwer auf dieses Feature.

Hier ist ein Spielzeugbeispiel, wie sie funktionieren.

function highlight(strings, ...values) {
  const interpolation = strings.reduce((prev, current) => {
    return prev + current + (values.length ? "<mark>" + values.shift() + "</mark>" : "");
  }, "");

  return interpolation;
}

const condiment = "Marmelade";
const meal = "Toast";

highlight`Ich mag ${condiment} auf ${meal}.`;
// "Ich mag <mark>Marmelade</mark> auf <mark>Toast</mark>."

Ein interessanteres Beispiel:

function comma(strings, ...values) {
  return strings.reduce((prev, next) => {
    let value = values.shift() || [];
    value = value.join(", ");
    return prev + next + value;
  }, "");
}

const snacks = ['Äpfel', 'Bananen', 'Kirschen'];
comma`Ich mag ${snacks} zum Knabbern.`;
// "Ich mag Äpfel, Bananen, Kirschen zum Knabbern."

Externe Ressourcen

Imports / Exports

ES6-Module werden verwendet, um auf Variablen oder Funktionen in einem Modul zuzugreifen, die explizit von den Modulen exportiert wurden, die es importiert.

Ich empfehle dringend, sich die Ressourcen von MDN zu Import/Export anzusehen (siehe externe Ressourcen unten), da sie sowohl unkompliziert als auch vollständig sind.

Erklärung mit Beispielcode

Benannte Exports

Mit benannten Exports können mehrere Werte aus einem Modul exportiert werden.

Hinweis : Du kannst nur Erstklassige Bürger mit einem Namen benannt exportieren.

// mathConstants.js
export const pi = 3.14;
export const exp = 2.7;
export const alpha = 0.35;

// -------------

// myFile.js
import { pi, exp } from './mathConstants.js'; // Benannter Import -- Destructuring-ähnliche Syntax
console.log(pi) // 3.14
console.log(exp) // 2.7

// -------------

// mySecondFile.js
import * as constants from './mathConstants.js'; // Injiziere alle exportierten Werte in die Variable constants
console.log(constants.pi) // 3.14
console.log(constants.exp) // 2.7

Obwohl benannte Imports wie Destructuring aussehen, haben sie eine andere Syntax und sind nicht dasselbe. Sie unterstützen keine Standardwerte oder tiefes Destructuring.

Außerdem kannst du Aliase verwenden, aber die Syntax ist anders als die, die beim Destructuring verwendet wird:

import { foo as bar } from 'myFile.js'; // foo wird importiert und in eine neue bar Variable injiziert
Standardimport / export

Bezüglich des Standardexports gibt es nur einen einzigen Standardexport pro Modul. Ein Standardexport kann eine Funktion, eine Klasse, ein Objekt oder irgendetwas anderes sein. Dieser Wert wird als der “Haupt”-exportierte Wert angesehen, da er am einfachsten zu importieren ist. Ref: MDN

// coolNumber.js
const ultimateNumber = 42;
export default ultimateNumber;

// ------------

// myFile.js
import number from './coolNumber.js';
// Standardexport, unabhängig von seinem Namen, wird automatisch in die Variable number injiziert;
console.log(number) // 42

Exportieren einer Funktion:

// sum.js
export default function sum(x, y) {
  return x + y;
}
// -------------

// myFile.js
import sum from './sum.js';
const result = sum(1, 2);
console.log(result) // 3

Externe Ressourcen

JavaScript this

Der this-Operator verhält sich anders als in anderen Sprachen und wird in den meisten Fällen durch die Art und Weise bestimmt, wie eine Funktion aufgerufen wird. (Ref: MDN).

Diese Vorstellung hat viele Feinheiten und ist ziemlich schwierig, daher schlage ich vor, in die unten aufgeführten externen Ressourcen einzutauchen. Ich werde das wiedergeben, was ich persönlich im Kopf habe, um zu bestimmen, was this gleich ist. Ich habe diesen Tipp aus diesem Artikel von Yehuda Katz gelernt.

function myFunc() {
  ...
}

// Nach jeder Anweisung findest du den Wert von *this* in myFunc

myFunc.call("myString", "hallo") // "myString" -- der erste .call-Parameterwert wird in *this* injiziert

// Im Nicht-Strict-Modus
myFunc("hallo") // window -- myFunc() ist syntaktischer Zucker für myFunc.call(window, "hallo")

// Im Strict-Modus
myFunc("hallo") // undefined -- myFunc() ist syntaktischer Zucker für myFunc.call(undefined, "hallo")
var person = {
  myFunc: function() { ... }
}

person.myFunc.call(person, "test") // person Objekt -- der erste call-Parameter wird in *this* injiziert
person.myFunc("test") // person Objekt -- person.myFunc() ist syntaktischer Zucker für person.myFunc.call(person, "test")

var myBoundFunc = person.myFunc.bind("hallo") // Erstellt eine neue Funktion, in der wir "hallo" in den *this*-Wert injizieren
person.myFunc("test") // person Objekt -- Die bind-Methode hat keine Auswirkung auf die Originalmethode
myBoundFunc("test") // "hallo" -- myBoundFunc ist person.myFunc mit "hallo" gebunden an *this*

Externe Ressourcen

Klasse

JavaScript ist eine prototypbasierte Sprache (während Java beispielsweise eine klassenbasierte Sprache ist). ES6 hat JavaScript-Klassen eingeführt, die als syntaktischer Zucker für die prototypbasierte Vererbung gedacht sind und kein neues klassenbasiertes Vererbungsmodell darstellen (ref).

Das Wort class ist tatsächlich irreführend, wenn du mit Klassen in anderen Sprachen vertraut bist. Wenn du es bist, vermeide Annahmen darüber, wie JavaScript-Klassen funktionieren, auf dieser Grundlage und betrachte es als eine völlig unterschiedliche Vorstellung.

Da dieses Dokument nicht den Anspruch hat, dir die Sprache von Grund auf beizubringen, gehe ich davon aus, dass du weißt, was Prototypen sind und wie sie sich verhalten. Wenn nicht, sieh dir die unten aufgeführten externen Ressourcen nach dem Beispielcode an.

Beispiele

Vor ES6, Prototyp-Syntax:

var Person = function(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.stringSentence = function() {
  return "Hallo, mein Name ist " + this.name + " und ich bin " + this.age;
}

Mit ES6-Klassensyntax:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  stringSentence() {
    return `Hallo, mein Name ist ${this.name} und ich bin ${this.age}`;
  }
}

const myPerson = new Person("Manu", 23);
console.log(myPerson.age) // 23
console.log(myPerson.stringSentence()) // "Hallo, mein Name ist Manu und ich bin 23

Externe Ressourcen

Zum Verständnis von Prototypen:

Zum Verständnis von Klassen:

Extends und super

Das Schlüsselwort extends wird in Klassendeklarationen oder Klassenausdrücken verwendet, um eine Klasse zu erstellen, die ein Kind einer anderen Klasse ist (Ref: MDN). Die Unterklasse erbt alle Eigenschaften der Oberklasse und kann zusätzlich neue Eigenschaften hinzufügen oder die geerbten modifizieren.

Das Schlüsselwort super wird verwendet, um Funktionen eines Oberobjekts aufzurufen, einschließlich seines Konstruktors.

Beispielcode

class Polygon {
  constructor(height, width) {
    this.name = 'Polygon';
    this.height = height;
    this.width = width;
  }

  getHelloPhrase() {
    return `Hallo, ich bin ein ${this.name}`;
  }
}

class Square extends Polygon {
  constructor(length) {
    // Hier wird der Konstruktor der Oberklasse mit den Längen
    // für die Breite und Höhe des Polygons aufgerufen
    super(length, length);
    // Hinweis: In abgeleiteten Klassen muss super() aufgerufen werden, bevor du
    // 'this' verwenden kannst. Wenn du dies auslässt, wird ein Referenzfehler verursacht.
    this.name = 'Quadrat';
    this.length = length;
  }

  getCustomHelloPhrase() {
    const polygonPhrase = super.getHelloPhrase(); // Zugriff auf die Elternmethode mit super.X()-Syntax
    return `${polygonPhrase} mit einer Länge von ${this.length}`;
  }

  get area() {
    return this.height * this.width;
  }
}

const mySquare = new Square(10);
console.log(mySquare.area) // 100
console.log(mySquare.getHelloPhrase()) // 'Hallo, ich bin ein Quadrat' -- Square erbt von Polygon und hat Zugriff auf seine Methoden
console.log(mySquare.getCustomHelloPhrase()) // 'Hallo, ich bin ein Quadrat mit einer Länge von 10'

Hinweis : Wenn wir versucht hätten, this vor dem Aufruf von super() in der Square-Klasse zu verwenden, wäre ein ReferenceError ausgelöst worden:

class Square extends Polygon {
  constructor(length) {
    this.height; // ReferenceError, super muss zuerst aufgerufen werden!

    // Hier wird der Konstruktor der Oberklasse mit den Längen
    // für die Breite und Höhe des Polygons aufgerufen
    super(length, length);

    // Hinweis: In abgeleiteten Klassen muss super() aufgerufen werden bevor du
    // 'this' verwenden kannst. Wenn du dies auslässt, wird ein Referenzfehler verursacht.
    this.name = 'Quadrat';
  }
}

Externe Ressourcen

Async Await

Zusätzlich zu Promises gibt es eine neue Syntax, die du vielleicht antriffst, um asynchronen Code zu handhaben, nämlich async / await.

Der Zweck von async/await-Funktionen ist es, die Verwendung von Promises synchron zu vereinfachen und ein Verhalten für eine Gruppe von Promises durchzuführen. Genau wie Promises ähnlich wie strukturierte Callbacks sind, ist async/await ähnlich wie die Kombination von Generatoren und Promises. Async-Funktionen geben immer ein Promise zurück. (Ref: MDN)

Hinweis : Du musst verstehen, was Promises sind und wie sie funktionieren, bevor du versuchst, async / await zu verstehen, da diese darauf basieren.

Hinweis 2: await muss in einer async Funktion verwendet werden, was bedeutet, dass du await nicht auf der obersten Ebene unseres Codes verwenden kannst, da dies nicht innerhalb einer async-Funktion ist.

Beispielcode

async function getGithubUser(username) { // das async-Schlüsselwort ermöglicht die Verwendung von await in der Funktion und bedeutet, dass die Funktion ein Promise zurückgibt
  const response = await fetch(`https://api.github.com/users/${username}`); // Die Ausführung wird hier pausiert, bis das von fetch zurückgegebene Promise aufgelöst ist
  return response.json();
}

getGithubUser('mbeaudru')
  .then(user => console.log(user)) // das Nutzer-Response loggen - await-Syntax kann nicht verwendet werden, da dieser Code nicht in einer async-Funktion ist
  .catch(err => console.log(err)); // wenn ein Fehler in unserer async-Funktion geworfen wird, werden wir ihn hier erwischen

Erklärung mit Beispielcode

Async / Await basiert auf Promises, ermöglicht aber eine imperativere Code-Stil.

Der async-Operator markiert eine Funktion als asynchron und wird immer ein Promise zurückgeben. Du kannst den await-Operator in einer async-Funktion verwenden, um die Ausführung in dieser Zeile zu pausieren, bis das vom Ausdruck zurückgegebene Promise entweder aufgelöst oder abgelehnt wird.

async function myFunc() {
  // wir können den await-Operator verwenden, weil diese Funktion async ist
  return "hallo welt";
}

myFunc().then(msg => console.log(msg)) // "hallo welt" -- der Rückgabewert von myFunc wird durch den async-Operator in ein Promise umgewandelt

Wenn das return-Statement einer async-Funktion erreicht wird, wird das Promise mit dem zurückgegebenen Wert erfüllt. Wenn innerhalb einer async-Funktion ein Fehler geworfen wird, wechselt der Promise-Zustand zu abgelehnt. Wenn aus einer async-Funktion kein Wert zurückgegeben wird, wird trotzdem ein Promise zurückgegeben und löst ohne Wert auf, wenn die Ausführung der async-Funktion abgeschlossen ist.

Der await-Operator wird verwendet, um auf die Erfüllung eines Promise zu warten und kann nur im Körper einer async-Funktion verwendet werden. Beim Auftreffen wird die Codeausführung pausiert, bis das Promise erfüllt ist.

Hinweis : fetch ist eine Funktion, die ein Promise zurückgibt, das einen AJAX-Anfrage ermöglicht

Sehen wir uns an, wie wir einen Github-Nutzer mit Promises zuerst abrufen würden:

function getGithubUser(username) {
  return fetch(`https://api.github.com/users/${username}`).then(response => response.json());
}

getGithubUser('mbeaudru')
  .then(user => console.log(user))
  .catch(err => console.log(err));

Hier ist das äquivalente async / await:

async function getGithubUser(username) { // promise + await-Schlüsselwortverwendung erlaubt
  die Antwort = await fetch(`https://api.github.com/users/${username}`); // Die Ausführung stoppt hier, bis das fetch-Promise erfüllt ist
  return response.json();
}

getGithubUser('mbeaudru')
  .then(user => console.log(user))
  .catch(err => console.log(err));

Die async / await-Syntax ist besonders praktisch, wenn du Promises verketten musst, die voneinander abhängig sind.

Wenn du beispielsweise einen Token abrufen musst, um einen Blogeintrag in einer Datenbank und dann die Autoreninformationen abzurufen:

Hinweis : await-Ausdrücke müssen in Klammern eingefasst werden, um Methoden und Eigenschaften des aufgelösten Werts auf derselben Zeile aufzurufen.

async function fetchPostById(postId) {
  const token = (await fetch('token_url')).json().token;
  const post = (await fetch(`/posts/${postId}?token=${token}`)).json();
  der Autor = (await fetch(`/users/${post.authorId}`)).json();

  post.author = author;
  return post;
}

fetchPostById('gzIrzeo64')
  .then(post => console.log(post))
  .catch(err => console.log(err));
Fehlerbehandlung

Sofern wir try / catch-Blöcke nicht um await-Ausdrücke herum hinzufügen, werden unerfasste Ausnahmen – unabhängig davon, ob sie im Körper Ihrer async-Funktion oder während ihrer Unterbrechung während await geworfen wurden – das Promise, das von der async-Funktion zurückgegeben wird, ablehnen. Die Verwendung des throw-Statements in einer asynchronen Funktion ist dasselbe wie die Rückgabe eines Promises, das abgelehnt wird. (Ref: PonyFoo).

Hinweis : Promises verhalten sich gleich!

Mit Promises sieht die Fehlerbearbeitungskette so aus:

function getUser() { // Dieses Promise wird abgelehnt!
  return new Promise((res, rej) => rej("Benutzer nicht gefunden!"));
}

function getAvatarByUsername(userId) {
  return getUser(userId).then(user => user.avatar);
}

function getUserAvatar(username) {
  return getAvatarByUsername(username).then(avatar => ({ username, avatar }));
}

getUserAvatar('mbeaudru')
  .then(res => console.log(res))
  .catch(err => console.log(err)); // "Benutzer nicht gefunden!"

Das Äquivalent mit async / await:

async function getUser() { // Das zurückgegebene Promise wird abgelehnt sein!
  wirf "Benutzer nicht gefunden!";
}

async function getAvatarByUsername(userId) => {
  const user = await getUser(userId);
  return user.avatar;
}

async function getUserAvatar(username) {
  var avatar = await getAvatarByUsername(username);
  return { username, avatar };
}

getUserAvatar('mbeaudru')
  .then(res => console.log(res))
  .catch(err => console.log(err)); // "Benutzer nicht gefunden!"

Externe Ressourcen

Truthy / Falsy

In JavaScript ist ein Truthy- oder Falsy-Wert ein Wert, der in einen Boolean umgewandelt wird, wenn er in einem booleschen Kontext ausgewertet wird. Ein Beispiel für einen booleschen Kontext wäre die Auswertung einer if-Bedingung:

Jeder Wert wird zu true umgewandelt, es sei denn, sie sind gleich:

Hier sind Beispiele für booleschen Kontext:

if (myVar) {}

myVar kann ein first-class citizen (Variable, Funktion, Boolean) sein, aber es wird in einen Boolean umgewandelt, weil es in einem booleschen Kontext ausgewertet wird.

Dieser Operator gibt false zurück, wenn sein einzelner Operand in true umgewandelt werden kann; andernfalls gibt er true zurück.

!0 // true -- 0 ist falsy, also gibt es true zurück
!!0 // false -- 0 ist falsy, also gibt !0 true zurück, daher liefert !(!0) false
!!"" // false -- leerer String ist falsy, also NICHT (NICHT false) ist gleich false
new Boolean(0) // false
new Boolean(1) // true
myVar ? "truthy" : "falsy"

myVar wird in einem booleschen Kontext ausgewertet.

Sei vorsichtig beim Vergleichen von 2 Werten. Die Objektwerte (die zu true umgewandelt werden sollten) werden nicht in einen Boolean umgewandelt, sondern es wird gezwungen, in einen primitiven Wert mithilfe der ToPrimitives-Spezifikation umgewandelt. Intern, wenn ein Objekt mit einem Boolean-Wert wie [] == true verglichen wird, macht es [].toString() == true, also…

let a = [] == true // a ist false, da [].toString() "" zurückgibt.
let b = [1] == true // b ist true, da [1].toString() "1" zurückgibt.
let c = [2] == true // c ist false, da [2].toString() "2" zurückgibt.

Externe Ressourcen

Anamorphismen und Katamorphismen

Anamorphismen

Anamorphismen sind Funktionen, die von einem Objekt auf eine komplexere Struktur abbilden, die den Typ des Objekts enthält. Es ist der Prozess des Entfaltens einer einfachen Struktur in eine komplexere. Betrachte das Entfalten einer Ganzzahl in eine Liste von Ganzzahlen. Die Ganzzahl ist unser Ausgangsobjekt und die Liste von Ganzzahlen ist die komplexere Struktur.

Beispielcode

function downToOne(n) {
  const list = [];

  for (let i = n; i > 0; --i) {
    list.push(i);
  }

  return list;
}

downToOne(5)
  //=> [ 5, 4, 3, 2, 1 ]

Katamorphismen

Katamorphismen sind das Gegenteil von Anamorphismen, da sie Objekte einer komplexeren Struktur nehmen und sie in einfachere Strukturen falten. Nehme das folgende Beispiel product, welches eine Liste von Ganzzahlen nimmt und eine einzelne Ganzzahl zurückgibt.

Beispielcode

function product(list) {
  let product = 1;

  for (const n of list) {
    product = product * n;
  }

  return product;
}

product(downToOne(5)) // 120

Externe Ressourcen

Generatoren

Eine andere Möglichkeit, die Funktion downToOne zu schreiben, ist die Verwendung eines Generators. Um ein Generator-Objekt zu instanziieren, muss man die function *-Deklaration verwenden. Generatoren sind Funktionen, die verlassen und später mit gespeichertem Kontext (Variablenbindungen) wieder betreten werden können.

Zum Beispiel kann die oben genannte Funktion downToOne wie folgt umgeschrieben werden:

function * downToOne(n) {
  for (let i = n; i > 0; --i) {
    yield i;
  }
}

[...downToOne(5)] // [ 5, 4, 3, 2, 1 ]

Generatoren geben ein iterierbares Objekt zurück. Wenn die next()-Funktion des Iterators aufgerufen wird, wird sie ausgeführt, bis zum ersten yield-Ausdruck, welcher den zurückzugebenden Wert vom Iterator angibt oder mit yield*, das an eine andere Generatorfunktion delegiert. Wenn in dem Generator eine return-Anweisung aufgerufen wird, wird der Generator als beendet markiert und gibt den Rückgabewert zurück. Weitere Aufrufe von next() geben keine neuen Werte zurück.

Beispielcode

// Yield-Beispiel
function * idMaker() {
  var index = 0;
  while (index < 2) {
    yield index;
    index = index + 1;
  }
}

var gen = idMaker();

gen.next().value; // 0
gen.next().value; // 1
gen.next().value; // undefined

Der yield*-Ausdruck ermöglicht einem Generator, eine andere Generatorfunktion während der Iteration aufzurufen.

// Yield * Beispiel
function * genB(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}

function * genA(i) {
  yield i;
  yield* genB(i);
  yield i + 10;
}

var gen = genA(10);

gen.next().value; // 10
gen.next().value; // 11
gen.next().value; // 12
gen.next().value; // 13
gen.next().value; // 20
// Generator Return-Beispiel
function* yieldAndReturn() {
  yield "Y";
  return "R";
  yield "unerreichbar";
}

var gen = yieldAndReturn()
gen.next(); // { value: "Y", done: false }
gen.next(); // { value: "R", done: true }
gen.next(); // { value: undefined, done: true }

Externe Ressourcen

Statische Methoden

Kurze Erklärung

Das Schlüsselwort static wird in Klassen verwendet, um statische Methoden zu deklarieren. Statische Methoden sind Funktionen in einer Klasse, die zum Klassenobjekt gehören und nicht für irgendeine Instanz dieser Klasse verfügbar sind.

Beispielcode

class Repo {
  static getName() {
    return "Repo name is modern-js-cheatsheet"
  }
}

// Beachte, dass wir keine Instanz der Repo-Klasse erstellen mussten
console.log(Repo.getName()) // Der Name des Repos ist modern-js-cheatsheet

let r = new Repo();
console.log(r.getName()) // Ungefangener TypeError: r.getName ist keine Funktion

Ausführliche Erklärung

Statische Methoden können innerhalb einer anderen statischen Methode mithilfe des Schlüsselworts this aufgerufen werden, dies funktioniert jedoch nicht für nicht-statische Methoden. Nicht-statische Methoden können nicht direkt auf statische Methoden mit dem Schlüsselwort this zugreifen.

Andere statische Methoden aus einer statischen Methode aufrufen.

Um eine statische Methode aus einer anderen statischen Methode aufzurufen, kann das Schlüsselwort this verwendet werden wie folgt;

class Repo {
  static getName() {
    return "Repo name is modern-js-cheatsheet"
  }

  static modifyName() {
    return this.getName() + '-added-this'
  }
}

console.log(Repo.modifyName()) // Der Name des Repos ist modern-js-cheatsheet-added-this
Statische Methoden aus nicht-statischen Methoden aufrufen.

Nicht-statische Methoden können auf zwei Weisen statische Methoden aufrufen;

  1. Mit dem Klassennamen.

Um Zugriff auf eine statische Methode von einer nicht-statischen Methode zu bekommen, verwenden wir den Klassennamen und rufen die statische Methode wie eine Eigenschaft auf. z.B ClassName.StaticMethodName

class Repo {
  static getName() {
    return "Repo name is modern-js-cheatsheet"
  }

  useName() {
    return Repo.getName() + ' and it contains some really important stuff'
  }
}

// Wir müssen die Klasse instanziieren, um nicht-statische Methoden zu verwenden
let r = new Repo()
console.log(r.useName()) // Der Name des Repos ist modern-js-cheatsheet und enthält einige wirklich wichtige Dinge
  1. Mit dem Konstruktor

Statische Methoden können als Eigenschaften des Konstruktorobjekts aufgerufen werden.

class Repo {
  static getName() {
    return "Repo name is modern-js-cheatsheet"
  }

  useName() {
    // Ruft die statische Methode als Eigenschaft des Konstruktors auf
    return this.constructor.getName() + ' and it contains some really important stuff'
  }
}

// Wir müssen die Klasse instanziieren, um nicht-statische Methoden zu verwenden
let r = new Repo()
console.log(r.useName()) // Der Name des Repos ist modern-js-cheatsheet und enthält einige wirklich wichtige Dinge

Externe Ressourcen

Glossar

Scope

Der Kontext, in dem Werte und Ausdrücke “sichtbar” sind oder referenziert werden können. Wenn eine Variable oder ein anderer Ausdruck “nicht im aktuellen Scope” ist, dann ist er nicht zur Verwendung verfügbar.

Quelle: MDN

Variablenmutation

Von einer Variablen wird gesagt, dass sie mutiert wurde, wenn ihr anfänglicher Wert sich danach geändert hat.

var myArray = [];
myArray.push("firstEl") // myArray wird mutiert

Eine Variable wird als unveränderlich bezeichnet, wenn sie nicht mutiert werden kann.

Schau dir den MDN Mutable-Artikel an für weitere Details.