this schlechte Nicht-Methoden-Funktionenthat = thisthisbind(this)bind()this über Parameter=>), aber keine „dünnen“ Pfeilfunktionen (->)?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
argumentssuperthisnew.targetthis schlechte Nicht-Methoden-Funktionen In JavaScript können traditionelle Funktionen verwendet werden als
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.
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' ]
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)
};
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)
};
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.
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);
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 ]
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
}
Die Quelle von this ist ein wichtiger Unterscheidungsaspekt von Pfeilfunktionen.
this*; sein Wert wird durch den Aufruf bestimmt.this*; sein Wert wird durch den umgebenden Gültigkeitsbereich bestimmt.Die vollständige Liste der Variablen, deren Werte lexikalisch bestimmt werden, ist
argumentssuperthisnew.targetEs gibt einige Details im Zusammenhang mit der Syntax, die Sie manchmal stolpern lassen können.
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;
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).
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;
}
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 });
Einige Teile der JavaScript-Syntax sind mehrdeutig. Nehmen Sie zum Beispiel den folgenden Code.
{
bar: 123
}
Es könnte sein
bar.bar und der Ausdrucksanweisung 123.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
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
})();
Ä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).
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.
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.
bind() ES6-Pfeilfunktionen sind oft eine überzeugende Alternative zu Function.prototype.bind().
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));
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]
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);
Eine Pfeilfunktion unterscheidet sich von einer normalen Funktion nur in zwei Punkten.
arguments, super, this, new.targetnew über die interne Methode [[Construct]] und die Eigenschaft prototype. Pfeilfunktionen haben keines von beiden, weshalb new (() => {}) einen Fehler auslöst.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.
=>), 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.