13. Pfeilfunktionen
Inhaltsverzeichnis
Bitte unterstützen Sie dieses Buch: kaufen Sie es (PDF, EPUB, MOBI) oder spenden Sie
(Werbung, bitte nicht blockieren.)

13. Pfeilfunktionen



13.1 Überblick

Pfeilfunktionen bieten zwei Vorteile.

Erstens sind sie weniger umständlich als traditionelle Funktionsausdrücke

const arr = [1, 2, 3];
const squares = arr.map(x => x * x);

// Traditional function expression:
const squares = arr.map(function (x) { return x * x });

Zweitens wird ihr this aus der Umgebung übernommen (lexikalisch). Daher benötigen Sie bind() oder that = this nicht mehr.

function UiComponent() {
    const button = document.getElementById('myButton');
    button.addEventListener('click', () => {
        console.log('CLICK');
        this.handleClick(); // lexical `this`
    });
}

Die folgenden Variablen sind alle lexikalisch innerhalb von Pfeilfunktionen

13.2 Traditionelle Funktionen sind wegen this schlechte Nicht-Methoden-Funktionen

In JavaScript können traditionelle Funktionen verwendet werden als

  1. Nicht-Methoden-Funktionen
  2. Methoden
  3. Konstruktoren

Diese Rollen stehen im Konflikt: Aufgrund der Rollen 2 und 3 haben Funktionen immer ihr eigenes this. Das hindert Sie jedoch daran, aus einem Callback (Rolle 1) auf das this einer umgebenden Methode zuzugreifen.

Das sehen Sie im folgenden ES5-Code

function Prefixer(prefix) {
    this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) { // (A)
    'use strict';
    return arr.map(function (x) { // (B)
        // Doesn’t work:
        return this.prefix + x; // (C)
    });
};

In Zeile C möchten wir auf this.prefix zugreifen, können es aber nicht, da das this der Funktion aus Zeile B das this der Methode aus Zeile A überschattet. Im Strict Mode ist this in Nicht-Methoden-Funktionen undefined, weshalb wir einen Fehler erhalten, wenn wir Prefixer verwenden.

> var pre = new Prefixer('Hi ');
> pre.prefixArray(['Joe', 'Alex'])
TypeError: Cannot read property 'prefix' of undefined

Es gibt drei Möglichkeiten, dieses Problem in ECMAScript 5 zu umgehen.

13.2.1 Lösung 1: that = this

Sie können this einer Variablen zuweisen, die nicht überschattet wird. Das ist es, was in der folgenden Zeile A geschieht.

function Prefixer(prefix) {
    this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
    var that = this; // (A)
    return arr.map(function (x) {
        return that.prefix + x;
    });
};

Jetzt funktioniert Prefixer wie erwartet

> var pre = new Prefixer('Hi ');
> pre.prefixArray(['Joe', 'Alex'])
[ 'Hi Joe', 'Hi Alex' ]

13.2.2 Lösung 2: Angabe eines Werts für this

Einige Array-Methoden haben einen zusätzlichen Parameter, um den Wert anzugeben, den this beim Aufrufen des Callbacks haben soll. Das ist der letzte Parameter in Zeile A unten.

function Prefixer(prefix) {
    this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
    return arr.map(function (x) {
        return this.prefix + x;
    }, this); // (A)
};

13.2.3 Lösung 3: bind(this)

Sie können die Methode bind() verwenden, um eine Funktion, deren this durch den Aufruf bestimmt wird (über call(), einen Funktionsaufruf, einen Methodenaufruf usw.), in eine Funktion umzuwandeln, deren this immer derselbe feste Wert ist. Das ist es, was wir in Zeile A unten tun.

function Prefixer(prefix) {
    this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
    return arr.map(function (x) {
        return this.prefix + x;
    }.bind(this)); // (A)
};

13.2.4 ECMAScript 6-Lösung: Pfeilfunktionen

Pfeilfunktionen funktionieren ähnlich wie Lösung 3. Man sollte sie jedoch als eine neue Art von Funktionen betrachten, die this nicht lexikalisch überschatten. Das heißt, sie unterscheiden sich von normalen Funktionen (man könnte sogar sagen, sie tun weniger). Sie sind keine normalen Funktionen plus Bindung.

Mit einer Pfeilfunktion sieht der Code wie folgt aus.

function Prefixer(prefix) {
    this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
    return arr.map((x) => {
        return this.prefix + x;
    });
};

Um den Code vollständig auf ES6 umzustellen, würden Sie eine Klasse und eine kompaktere Variante von Pfeilfunktionen verwenden.

class Prefixer {
    constructor(prefix) {
        this.prefix = prefix;
    }
    prefixArray(arr) {
        return arr.map(x => this.prefix + x); // (A)
    }
}

In Zeile A sparen wir ein paar Zeichen, indem wir zwei Teile der Pfeilfunktion anpassen.

13.3 Syntax von Pfeilfunktionen

Der „dicke“ Pfeil => (im Gegensatz zum dünnen Pfeil ->) wurde gewählt, um mit CoffeeScript kompatibel zu sein, dessen dicke Pfeilfunktionen sehr ähnlich sind.

Angabe von Parametern

    () => { ... } // no parameter
     x => { ... } // one parameter, an identifier
(x, y) => { ... } // several parameters

Angabe eines Körpers

x => { return x * x }  // block
x => x * x  // expression, equivalent to previous line

Der Anweisungsblock verhält sich wie ein normaler Funktionskörper. Zum Beispiel benötigen Sie return, um einen Wert zurückzugeben. Bei einem Ausdruckskörper wird der Ausdruck immer implizit zurückgegeben.

Beachten Sie, wie sehr eine Pfeilfunktion mit einem Ausdruckskörper die Umständlichkeit reduzieren kann. Vergleichen Sie

const squares = [1, 2, 3].map(function (x) { return x * x });
const squares = [1, 2, 3].map(x => x * x);

13.3.1 Klammern bei einzelnen Parametern weglassen

Das Weglassen der Klammern um die Parameter ist nur möglich, wenn sie aus einem einzelnen Bezeichner bestehen.

> [1,2,3].map(x => 2 * x)
[ 2, 4, 6 ]

Sobald etwas anderes vorhanden ist, müssen Sie die Klammern tippen, auch wenn nur ein einzelner Parameter vorhanden ist. Zum Beispiel benötigen Sie Klammern, wenn Sie einen einzelnen Parameter entpacken.

> [[1,2], [3,4]].map(([a,b]) => a + b)
[ 3, 7 ]

Und Sie benötigen Klammern, wenn ein einzelner Parameter einen Standardwert hat (undefined löst den Standardwert aus!).

> [1, undefined, 3].map((x='yes') => x)
[ 1, 'yes', 3 ]

13.4 Lexikalische Variablen

13.4.1 Weitergabe von Variablenwerten: statisch versus dynamisch

Die folgenden sind zwei Möglichkeiten, wie die Werte von Variablen weitergegeben werden können.

Erstens statisch (lexikalisch): Wo eine Variable zugänglich ist, wird durch die Struktur des Programms bestimmt. In einem Gültigkeitsbereich deklarierte Variablen sind in allen darin verschachtelten Gültigkeitsbereichen zugänglich (sofern nicht überschattet). Zum Beispiel

const x = 123;

function foo(y) {
    return x; // value received statically
}

Zweitens dynamisch: Variablenwerte können über Funktionsaufrufe weitergegeben werden. Zum Beispiel

function bar(arg) {
    return arg; // value received dynamically
}

13.4.2 Variablen, die in Pfeilfunktionen lexikalisch sind

Die Quelle von this ist ein wichtiger Unterscheidungsaspekt von Pfeilfunktionen.

Die vollständige Liste der Variablen, deren Werte lexikalisch bestimmt werden, ist

13.5 Syntaxfallen

Es gibt einige Details im Zusammenhang mit der Syntax, die Sie manchmal stolpern lassen können.

13.5.1 Pfeilfunktionen binden sehr lose

Wenn Sie => als Operator betrachten, könnten Sie sagen, dass er eine niedrige Präzedenz hat, dass er lose bindet. Das bedeutet, dass, wenn er mit anderen Operatoren in Konflikt gerät, diese normalerweise gewinnen.

Der Grund dafür ist, dass ein Ausdruckskörper „zusammenkleben“ kann.

const f = x => (x % 2) === 0 ? x : 0;

Anders ausgedrückt: Wir möchten, dass => den Kampf gegen === und ? verliert. Wir möchten, dass er wie folgt interpretiert wird:

const f = x => ((x % 2) === 0 ? x : 0);

Wenn => gegen beide gewinnen würde, sähe es so aus

const f = (x => (x % 2)) === 0 ? x : 0;

Wenn => gegen === verloren, aber gegen ? gewonnen hätte, sähe es so aus

const f = (x => ((x % 2) === 0)) ? x : 0;

Daher müssen Sie Pfeilfunktionen oft in Klammern setzen, wenn sie mit anderen Operatoren konkurrieren. Zum Beispiel

console.log(typeof () => {}); // SyntaxError
console.log(typeof (() => {})); // OK

Andererseits können Sie typeof als Ausdruckskörper verwenden, ohne ihn in Klammern zu setzen.

const f = x => typeof x;

13.5.2 Kein Zeilenumbruch nach Parametern von Pfeilfunktionen

ES6 verbietet einen Zeilenumbruch zwischen den Parameterdefinitionen und dem Pfeil einer Pfeilfunktion.

const func1 = (x, y) // SyntaxError
=> {
    return x + y;
};
const func2 = (x, y) => // OK
{
    return x + y;
};
const func3 = (x, y) => { // OK
    return x + y;
};

const func4 = (x, y) // SyntaxError
=> x + y;
const func5 = (x, y) => // OK
x + y;

Zeilenumbrüche *innerhalb* von Parameterdefinitionen sind in Ordnung.

const func6 = ( // OK
    x,
    y
) => {
    return x + y;
};

Der Grund für diese Einschränkung ist, dass sie Optionen für zukünftige „kopflose“ Pfeilfunktionen offen hält (Sie könnten die Klammern beim Definieren einer Pfeilfunktion mit null Parametern weglassen).

13.5.3 Anweisungen können keine Ausdruckskörper sein

13.5.3.1 Ausdrücke versus Anweisungen

Kurze Wiederholung (konsultieren Sie „Speaking JavaScript“ für weitere Informationen).

Ausdrücke erzeugen Werte (werden zu Werten ausgewertet). Beispiele

3 + 4
foo(7)
'abc'.length

Anweisungen tun Dinge. Beispiele

while (true) { ··· }
return 123;

Die meisten Ausdrücke1 können als Anweisungen verwendet werden, indem sie einfach an Anweisungsstellen erwähnt werden.

function bar() {
    3 + 4;
    foo(7);
    'abc'.length;
}
13.5.3.2 Die Körper von Pfeilfunktionen

Wenn ein Ausdruck der Körper einer Pfeilfunktion ist, benötigen Sie keine geschweiften Klammern.

asyncFunc.then(x => console.log(x));

Anweisungen müssen jedoch in geschweifte Klammern gesetzt werden.

asyncFunc.catch(x => { throw x });

13.5.4 Zurückgeben von Objektliteralen

Einige Teile der JavaScript-Syntax sind mehrdeutig. Nehmen Sie zum Beispiel den folgenden Code.

{
    bar: 123
}

Es könnte sein

Da der Körper einer Pfeilfunktion entweder ein Ausdruck oder eine Anweisung sein kann, müssen Sie ein Objektliteral in Klammern setzen, wenn es ein Ausdruckskörper sein soll.

> const f1 = x => ({ bar: 123 });
> f1()
{ bar: 123 }

Zum Vergleich: Dies ist eine Pfeilfunktion, deren Körper ein Block ist.

> const f2 = x => { bar: 123 };
> f2()
undefined

13.6 Sofort aufgerufene Pfeilfunktionen

Erinnern Sie sich an Sofort aufgerufene Funktionsausdrücke (IIFEs)? Sie sehen so aus und werden in ECMAScript 5 verwendet, um Block-Scoping und wertrückgebende Blöcke zu simulieren.

(function () { // open IIFE
    // inside IIFE
})(); // close IIFE

Sie können ein paar Zeichen sparen, wenn Sie eine sofort aufgerufene Pfeilfunktion (IIAF) verwenden.

(() => {
    return 123
})();

13.6.1 Semikolons

Ähnlich wie bei IIFEs sollten Sie IIAFs mit Semikolons beenden (oder eine äquivalente Maßnahme verwenden), um zu vermeiden, dass zwei aufeinanderfolgende IIAFs als Funktionsaufruf interpretiert werden (die erste als Funktion, die zweite als Parameter).

13.6.2 Klammerung von Pfeilfunktionen mit Blockkörpern

Auch wenn die IIAF einen Blockkörper hat, müssen Sie sie in Klammern setzen, da sie nicht (direkt) aufrufbar ist. Der Grund für diese syntaktische Einschränkung ist die Konsistenz mit Pfeilfunktionen, deren Körper Ausdrücke sind (wie im Folgenden erläutert).

Folglich müssen die Klammern um die Pfeilfunktion stehen. Im Gegensatz dazu haben Sie bei IIFEs die Wahl – Sie können entweder die Klammern um den gesamten Ausdruck setzen.

(function () {
    ···
}());

Oder nur um den Funktionsausdruck.

(function () {
    ···
})();

Angesichts der Funktionsweise von Pfeilfunktionen sollte die letztere Klammerung von nun an bevorzugt werden.

13.6.3 Klammerung von Pfeilfunktionen mit Ausdruckskörpern

Wenn Sie verstehen wollen, warum Sie eine Pfeilfunktion nicht aufrufen können, indem Sie direkt Klammern dahinter setzen, müssen Sie sich ansehen, wie Ausdruckskörper funktionieren: Klammern nach einem Ausdruckskörper sollten Teil des Ausdrucks sein, nicht ein Aufruf der gesamten Pfeilfunktion. Das hat damit zu tun, dass Pfeilfunktionen lose binden, wie in einem vorherigen Abschnitt erklärt.

Betrachten wir ein Beispiel.

const value = () => foo();

Dies sollte interpretiert werden als

const value = () => (foo());

Und nicht als

const value = (() => foo)();

Weiterführende Lektüre: Ein Abschnitt im Kapitel über aufrufbare Entitäten enthält weitere Informationen zur Verwendung von IIFEs und IIAFs in ES6. Spoiler: Sie benötigen sie selten, da ES6 oft bessere Alternativen bietet.

13.7 Pfeilfunktionen versus bind()

ES6-Pfeilfunktionen sind oft eine überzeugende Alternative zu Function.prototype.bind().

13.7.1 Extrahieren von Methoden

Wenn eine extrahierte Methode als Callback verwendet werden soll, müssen Sie ein festes this angeben, sonst wird sie als Funktion aufgerufen (und this ist undefined oder das globale Objekt). Zum Beispiel

obj.on('anEvent', this.handleEvent.bind(this));

Eine Alternative ist die Verwendung einer Pfeilfunktion.

obj.on('anEvent', event => this.handleEvent(event));

13.7.2 this über Parameter

Der folgende Code demonstriert einen cleveren Trick: Für einige Methoden benötigen Sie kein bind() für einen Callback, da sie es Ihnen erlauben, den Wert von this über einen zusätzlichen Parameter anzugeben. filter() ist eine solche Methode.

const as = new Set([1, 2, 3]);
const bs = new Set([3, 2, 4]);
const intersection = [...as].filter(bs.has, bs);
    // [2, 3]

Dieser Code ist jedoch leichter zu verstehen, wenn Sie eine Pfeilfunktion verwenden.

const as = new Set([1, 2, 3]);
const bs = new Set([3, 2, 4]);
const intersection = [...as].filter(a => bs.has(a));
    // [2, 3]

13.7.3 Partielle Auswertung

bind() ermöglicht Ihnen, partielle Auswertung durchzuführen, Sie können neue Funktionen erstellen, indem Sie Parameter einer bestehenden Funktion auffüllen.

function add(x, y) {
    return x + y;
}
const plus1 = add.bind(undefined, 1);

Auch hier finde ich eine Pfeilfunktion leichter zu verstehen.

const plus1 = y => add(1, y);

13.8 Pfeilfunktionen versus normale Funktionen

Eine Pfeilfunktion unterscheidet sich von einer normalen Funktion nur in zwei Punkten.

Davon abgesehen gibt es keine beobachtbaren Unterschiede zwischen einer Pfeilfunktion und einer normalen Funktion. Zum Beispiel ergeben typeof und instanceof dieselben Ergebnisse.

> typeof (() => {})
'function'
> () => {} instanceof Function
true

> typeof function () {}
'function'
> function () {} instanceof Function
true

Konsultieren Sie das Kapitel über aufrufbare Entitäten für weitere Informationen darüber, wann Sie Pfeilfunktionen und wann Sie traditionelle Funktionen verwenden sollten.

13.9 FAQ: Pfeilfunktionen

13.9.1 Warum gibt es in ES6 „dicke“ Pfeilfunktionen (=>), aber keine „dünnen“ Pfeilfunktionen (->)?

ECMAScript 6 verfügt über eine Syntax für Funktionen mit einem lexikalischen this, sogenannte *Pfeilfunktionen*. Es gibt jedoch keine Pfeilsyntax für Funktionen mit dynamischem this. Dieses Versäumnis war beabsichtigt; Methodendefinitionen decken die meisten Anwendungsfälle für dünne Pfeile ab. Wenn Sie unbedingt ein dynamisches this benötigen, können Sie immer noch einen traditionellen Funktionsausdruck verwenden.

Weiter: 14. Neue OOP-Features neben Klassen