Dieses Kapitel gibt Ratschläge, wie man aufrufbare Entitäten (mittels Funktionsaufrufen, Methodenaufrufen usw.) in ES6 korrekt verwendet.
super sind an bestimmte Orte gebundenObject.prototype und Array.prototypename von Funktionenname in der Spezifikationnew aufgerufen wurde?In ES5 spielte ein einziger Konstrukt, die (traditionelle) Funktion, drei Rollen
In ES6 gibt es mehr Spezialisierung. Die drei Aufgaben werden nun wie folgt erledigt. Was Funktionsdefinitionen und Klassendefinitionen angeht, so ist eine Definition entweder eine Deklaration oder ein Ausdruck.
Besonders für Callbacks sind Pfeilfunktionen praktisch, da sie das this des umgebenden Geltungsbereichs nicht überschatten.
Für längere Callbacks und eigenständige Funktionen können traditionelle Funktionen ebenfalls in Ordnung sein. Einige APIs verwenden this als impliziten Parameter. In diesem Fall haben Sie keine andere Wahl, als traditionelle Funktionen zu verwenden.
Beachten Sie, dass ich unterscheide zwischen
Auch wenn sich ihr Verhalten unterscheidet (wie später erklärt wird), sind all diese Entitäten Funktionen. Zum Beispiel
> typeof (() => {}) // arrow function
'function'
> typeof function* () {} // generator function
'function'
> typeof class {} // class
'function'
Einige Aufrufe können überall getätigt werden, andere sind an bestimmte Orte gebunden.
Drei Arten von Aufrufen können in ES6 überall getätigt werden
func(3, 1)obj.method('abc')new Constr(8)super sind an bestimmte Orte gebunden Zwei Arten von Aufrufen können über das Schlüsselwort super getätigt werden; ihre Verwendung ist an bestimmte Orte gebunden
super.method('abc')super(8)constructor() in einer abgeleiteten Klassendefinition verfügbar.Der Unterschied zwischen Nicht-Methoden-Funktionen und Methoden wird in ECMAScript 6 immer deutlicher. Es gibt nun spezielle Entitäten für beide und Dinge, die nur sie tun können
this aus ihren umgebenden Geltungsbereichen ("lexikalisches this").super, um auf Super-Eigenschaften zuzugreifen und Super-Methodenaufrufe zu tätigen.Dieser Abschnitt gibt Tipps zur Verwendung aufrufbarer Entitäten: Wann welche Entität am besten verwendet wird; usw.
Als Callbacks haben Pfeilfunktionen zwei Vorteile gegenüber traditionellen Funktionen
this ist lexikalisch und daher sicherer zu verwenden.this als implizites Argument Leider verwenden einige JavaScript-APIs this als implizites Argument für ihre Callbacks, was Sie an der Verwendung von Pfeilfunktionen hindert. Zum Beispiel: Das this in Zeile B ist ein implizites Argument der Funktion in Zeile A.
beforeEach(function () { // (A)
this.addMatchers({ // (B)
toBeInRange: function (start, end) {
···
}
});
});
Dieses Muster ist weniger explizit und hindert Sie an der Verwendung von Pfeilfunktionen.
Dies ist leicht zu beheben, erfordert aber eine Änderung der API
beforeEach(api => {
api.addMatchers({
toBeInRange(start, end) {
···
}
});
});
Wir haben die API von einem impliziten Parameter this zu einem expliziten Parameter api gemacht. Ich mag diese Art von Explizitheit.
this auf andere Weise zugreifen Bei einigen APIs gibt es alternative Möglichkeiten, an den Wert von this zu gelangen. Zum Beispiel verwendet der folgende Code this.
var $button = $('#myButton');
$button.on('click', function () {
this.classList.toggle('clicked');
});
Aber das Ziel des Ereignisses kann auch über event.target abgerufen werden
var $button = $('#myButton');
$button.on('click', event => {
event.target.classList.toggle('clicked');
});
Als eigenständige Funktionen (im Gegensatz zu Callbacks) bevorzuge ich Funktionsdeklarationen
function foo(arg1, arg2) {
···
}
Die Vorteile sind
function ein Vorteil – man möchte, dass der Konstrukt hervorsticht.Es gibt eine Einschränkung: Normalerweise benötigen Sie this nicht in eigenständigen Funktionen. Wenn Sie es verwenden, möchten Sie auf das this des umgebenden Geltungsbereichs zugreifen (z. B. eine Methode, die die eigenständige Funktion enthält). Leider erlauben Funktionsdeklarationen dies nicht – sie haben ihr eigenes this, das das this des umgebenden Geltungsbereichs überschattet. Daher möchten Sie vielleicht einen Linter verwenden, der Sie auf this in Funktionsdeklarationen hinweist.
Eine weitere Option für eigenständige Funktionen ist die Zuweisung von Pfeilfunktionen zu Variablen. Probleme mit this werden vermieden, da es lexikalisch ist.
const foo = (arg1, arg2) => {
···
};
Methodendefinitionen sind der einzige Weg, Methoden zu erstellen, die super verwenden. Sie sind die offensichtliche Wahl in Objektliteralen und Klassen (wo sie der einzige Weg sind, Methoden zu definieren), aber was ist mit dem Hinzufügen einer Methode zu einem vorhandenen Objekt? Zum Beispiel
MyClass.prototype.foo = function (arg1, arg2) {
···
};
Das Folgende ist ein schneller Weg, um dasselbe in ES6 zu tun (Vorbehalt: Object.assign() verschiebt Methoden mit super nicht korrekt).
Object.assign(MyClass.prototype, {
foo(arg1, arg2) {
···
}
});
Für weitere Informationen und Vorbehalte konsultieren Sie den Abschnitt über Object.assign().
Normalerweise sollten funktionswertige Eigenschaften über Methodendefinitionen erstellt werden. Gelegentlich sind Pfeilfunktionen jedoch die bessere Wahl. Die folgenden beiden Unterabschnitte erklären, was wann verwendet wird: Der erstere Ansatz ist besser für Objekte mit Methoden, der letztere Ansatz ist besser für Objekte mit Callbacks.
Erstellen Sie funktionswertige Eigenschaften über Methodendefinitionen, wenn diese Eigenschaften tatsächlich Methoden sind. Das ist der Fall, wenn die Eigenschaftswerte eng mit dem Objekt (obj im folgenden Beispiel) und seinen Geschwistermethoden verbunden sind und nicht mit dem umgebenden Geltungsbereich (surroundingMethod() im Beispiel).
Mit einer Methodendefinition ist das this eines Eigenschaftswerts der *Empfänger* des Methodenaufrufs (z. B. obj, wenn der Methodenaufruf obj.m(···) lautet).
Zum Beispiel können Sie die WHATWG Streams API wie folgt verwenden
const surroundingObject = {
surroundingMethod() {
const obj = {
data: 'abc',
start(controller) {
···
console.log(this.data); // abc (*)
this.pull(); // (**)
···
},
pull() {
···
},
cancel() {
···
},
};
const stream = new ReadableStream(obj);
},
};
obj ist ein Objekt, dessen Eigenschaften start, pull und cancel echte Methoden sind. Dementsprechend können diese Methoden this verwenden, um auf objektlokalen Zustand zuzugreifen (Zeile *) und sich gegenseitig aufzurufen (Zeile **).
Erstellen Sie funktionswertige Eigenschaften über Pfeilfunktionen, wenn die Eigenschaftswerte Callbacks sind. Solche Callbacks sind tendenziell eng mit ihren umgebenden Geltungsbereichen verbunden (surroundingMethod() im folgenden Beispiel) und nicht mit den Objekten, in denen sie gespeichert sind (obj im Beispiel).
Das this einer Pfeilfunktion ist das this des umgebenden Geltungsbereichs (*lexikalisches this*). Pfeilfunktionen eignen sich hervorragend als Callbacks, da dies das Verhalten ist, das Sie normalerweise für Callbacks (echte, nicht-Methoden-)Funktionen wünschen. Ein Callback sollte kein eigenes this haben, das das this des umgebenden Geltungsbereichs überschattet.
Wenn die Eigenschaften start, pull und cancel Pfeilfunktionen sind, übernehmen sie das this von surroundingMethod() (ihrem umgebenden Geltungsbereich)
const surroundingObject = {
surroundingData: 'xyz',
surroundingMethod() {
const obj = {
start: controller => {
···
console.log(this.surroundingData); // xyz (*)
···
},
pull: () => {
···
},
cancel: () => {
···
},
};
const stream = new ReadableStream(obj);
},
};
const stream = new ReadableStream();
Wenn die Ausgabe in Zeile * Sie überrascht, betrachten Sie den folgenden Code
const obj = {
foo: 123,
bar() {
const f = () => console.log(this.foo); // 123
const o = {
p: () => console.log(this.foo), // 123
};
},
}
Innerhalb der Methode bar() sollte das Verhalten von f unmittelbar einleuchten. Das Verhalten von o.p ist weniger offensichtlich, aber es ist dasselbe wie das von f. Beide Pfeilfunktionen haben denselben umgebenden lexikalischen Geltungsbereich, bar(). Die letztere Pfeilfunktion, die von einem Objektliteral umgeben ist, ändert daran nichts.
Dieser Abschnitt gibt Tipps zur Vermeidung von IIFEs in ES6.
In ES5 mussten Sie eine IIFE verwenden, wenn Sie eine Variable lokal halten wollten
(function () { // open IIFE
var tmp = ···;
···
}()); // close IIFE
console.log(tmp); // ReferenceError
In ECMAScript 6 können Sie einfach einen Block und eine let- oder const-Deklaration verwenden
{ // open block
let tmp = ···;
···
} // close block
console.log(tmp); // ReferenceError
In ECMAScript 5-Code, der keine Module über Bibliotheken (wie RequireJS, Browserify oder Webpack) verwendet, ist das *Revealing Module Pattern* beliebt und basiert auf einer IIFE. Sein Vorteil ist, dass es klar zwischen dem öffentlichen und dem privaten trennt
var my_module = (function () {
// Module-private variable:
var countInvocations = 0;
function myFunc(x) {
countInvocations++;
···
}
// Exported by module:
return {
myFunc: myFunc
};
}());
Dieses Modulmuster erzeugt eine globale Variable und wird wie folgt verwendet
my_module.myFunc(33);
In ECMAScript 6 sind Module integriert, weshalb die Hürde für deren Einführung niedrig ist
// my_module.js
// Module-private variable:
let countInvocations = 0;
export function myFunc(x) {
countInvocations++;
···
}
Dieses Modul erzeugt keine globale Variable und wird wie folgt verwendet
import { myFunc } from 'my_module.js';
myFunc(33);
Es gibt einen Anwendungsfall, in dem Sie in ES6 immer noch eine sofort aufgerufene Funktion benötigen: Manchmal können Sie ein Ergebnis nur über eine Abfolge von Anweisungen erzielen, nicht über einen einzelnen Ausdruck. Wenn Sie diese Anweisungen inline einfügen möchten, müssen Sie sofort eine Funktion aufrufen. In ES6 können Sie mit sofort aufgerufenen Pfeilfunktionen ein paar Zeichen sparen
const SENTENCE = 'How are you?';
const REVERSED_SENTENCE = (() => {
// Iteration over the string gives us code points
// (better for reversal than characters)
const arr = [...SENTENCE];
arr.reverse();
return arr.join('');
})();
Beachten Sie, dass Sie wie gezeigt Klammern setzen müssen (die Klammern umschließen die Pfeilfunktion, nicht den vollständigen Funktionsaufruf). Details werden im Kapitel über Pfeilfunktionen erläutert.
In ES5 waren Konstruktorfunktionen der Mainstream-Weg, um Objekte zu erstellen (aber es gab auch viele andere Techniken, einige arguably eleganter). In ES6 sind Klassen der Mainstream-Weg zur Implementierung von Konstruktorfunktionen. Mehrere Frameworks unterstützen sie als Alternativen zu ihren benutzerdefinierten Vererbungs-APIs.
Dieser Abschnitt beginnt mit einem Spickzettel, bevor jede ES6-aufrufbare Entität im Detail beschrieben wird.
Merkmale der von den Entitäten erzeugten Werte
| Funktionsdekl./Funktionsausdr. | Pfeil | Klasse | Methode | |
|---|---|---|---|---|
| Funktionsaufrufbar | ✔ | ✔ | × | ✔ |
| Konstruktoraufrufbar | ✔ | × | ✔ | × |
| Prototyp | F.p |
F.p |
SC | F.p |
Eigenschaft prototype |
✔ | × | ✔ | × |
Merkmale der ganzen Entitäten
| Funktionsdekl. | Funktionsausdr. | Pfeil | Klasse | Methode | |
|---|---|---|---|---|---|
| Hoisted (vorangestellt) | ✔ | × | |||
Erzeugt window-Eigenschaft. (1) |
✔ | × | |||
| Innerer Name (2) | × | ✔ | ✔ | × |
Merkmale der Körper der Entitäten
| Funktionsdekl. | Funktionsausdr. | Pfeil | Klasse (3) | Methode | |
|---|---|---|---|---|---|
this |
✔ | ✔ | lex | ✔ | ✔ |
new.target |
✔ | ✔ | lex | ✔ | ✔ |
super.prop |
× | × | lex | ✔ | ✔ |
super() |
× | × | × | ✔ | × |
Legende – Tabellenzellen
F.p: Function.prototypeFunction.prototype für Basisklassen. Die Details werden im Kapitel über Klassen erläutert.Legende – Fußnoten
Was ist mit Generatorfunktionen und -methoden? Diese funktionieren wie ihre nicht-generatorischen Gegenstücke, mit zwei Ausnahmen
(GeneratorFunction).prototype ((GeneratorFunction) ist ein internes Objekt, siehe Diagramm in Abschnitt „Vererbung innerhalb der Iterations-API (einschließlich Generatoren)“).this | Funktionsaufruf | Methodenaufruf | new |
|
|---|---|---|---|
| Traditionelle Funktion (strict) | undefined |
Empfänger | Instanz |
| Traditionelle Funktion (sloppy) | Fenster |
Empfänger | Instanz |
| Generatorfunktion (strict) | undefined |
Empfänger | TypeError |
| Generatorfunktion (sloppy) | Fenster |
Empfänger | TypeError |
| Methode (strict) | undefined |
Empfänger | TypeError |
| Methode (sloppy) | Fenster |
Empfänger | TypeError |
| Generator-Methode (strict) | undefined |
Empfänger | TypeError |
| Generator-Methode (sloppy) | Fenster |
Empfänger | TypeError |
| Pfeilfunktion (strict&sloppy) | lexikalisch | lexikalisch | TypeError |
| Klasse (implizit strict) | TypeError |
TypeError |
SC-Protokoll |
Legende – Tabellenzellen
this. Eine abgeleitete Klasse erhält ihre Instanz von ihrer Oberklasse. Die Details werden im Kapitel über Klassen erläutert.Dies sind die Funktionen, die Sie aus ES5 kennen. Es gibt zwei Möglichkeiten, sie zu erstellen
const foo = function (x) { ··· };
function foo(x) { ··· }
Regeln für this
this ist undefined in Strict-Mode-Funktionen und das globale Objekt im Sloppy-Mode.this ist der Empfänger des Methodenaufrufs (oder das erste Argument von call/apply).this ist die neu erstellte Instanz.Generatorfunktionen werden im Kapitel über Generatoren erläutert. Ihre Syntax ähnelt traditionellen Funktionen, aber sie haben ein zusätzliches Sternchen
const foo = function* (x) { ··· };
function* foo(x) { ··· }
Die Regeln für this sind wie folgt. Beachten Sie, dass sich this niemals auf das Generatorobjekt bezieht.
this wird wie bei traditionellen Funktionen behandelt. Die Ergebnisse solcher Aufrufe sind Generatorobjekte.TypeError wird ausgelöst, wenn Sie dies tun.Methodendefinitionen können innerhalb von Objektliteralen erscheinen
const obj = {
add(x, y) {
return x + y;
}, // comma is required
sub(x, y) {
return x - y;
}, // comma is optional
};
Und innerhalb von Klassendefinitionen
class AddSub {
add(x, y) {
return x + y;
} // no comma
sub(x, y) {
return x - y;
} // no comma
}
Wie Sie sehen, müssen Sie Methodendefinitionen in einem Objektliteral mit Kommas trennen, aber in einer Klassendefinition gibt es keine Trennzeichen dazwischen. Das erstere ist notwendig, um die Syntax konsistent zu halten, insbesondere in Bezug auf Getter und Setter.
Methodendefinitionen sind der einzige Ort, an dem Sie super verwenden können, um auf Super-Eigenschaften zu verweisen. Nur Methodendefinitionen, die super verwenden, erzeugen Funktionen, die die interne Eigenschaft [[HomeObject]] haben, was für diese Funktionalität erforderlich ist (Details werden im Kapitel über Klassen erläutert).
Regeln
super.TypeError.Innerhalb von Klassendefinitionen sind Methoden mit dem Namen constructor besonders, wie später in diesem Kapitel erläutert wird.
Generator-Methoden werden im Kapitel über Generatoren erläutert. Ihre Syntax ähnelt Methodendefinitionen, aber sie haben ein zusätzliches Sternchen
const obj = {
* generatorMethod(···) {
···
},
};
class MyClass {
* generatorMethod(···) {
···
}
}
Regeln
this und super verwenden, wie Sie es in normalen Methodendefinitionen tun würden.Pfeilfunktionen werden in einem eigenen Kapitel erläutert.
const squares = [1,2,3].map(x => x * x);
Die folgenden Variablen sind innerhalb einer Pfeilfunktion *lexikalisch* (vom umgebenden Geltungsbereich übernommen)
argumentssuperthisnew.targetRegeln
this usw.this bleibt lexikalisch und bezieht sich nicht auf den Empfänger eines Methodenaufrufs.TypeError.Klassen werden in einem eigenen Kapitel erläutert.
// Base class: no `extends`
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
// This class is derived from `Point`
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
toString() {
return super.toString() + ' in ' + this.color;
}
}
Die Methode constructor ist besonders, da sie zur Klasse "wird". Das heißt, Klassen sind sehr ähnlich zu Konstruktorfunktionen
> Point.prototype.constructor === Point
true
Regeln
this bezieht sich darauf. Eine abgeleitete Klasse erhält ihre Instanz von ihrer Oberklasse, weshalb sie super() aufrufen muss, bevor sie auf this zugreifen kann.Es gibt zwei Möglichkeiten, Methoden in JavaScript aufzurufen
arr.slice(1)): Die Eigenschaft slice wird in der Prototypenkette von arr gesucht. Ihr Ergebnis wird mit this auf arr gesetzt aufgerufen.Array.prototype.slice.call(arr, 1)): slice wird direkt aufgerufen, wobei this auf arr gesetzt wird (das erste Argument von call()).Dieser Abschnitt erklärt, wie diese beiden funktionieren und warum Sie in ECMAScript 6 selten Methoden direkt aufrufen werden. Bevor wir beginnen, frischen wir unser Wissen über Prototypketten auf.
Denken Sie daran, dass jedes Objekt in JavaScript tatsächlich eine Kette aus einem oder mehreren Objekten ist. Das erste Objekt erbt Eigenschaften von den späteren Objekten. Zum Beispiel sieht die Prototypenkette eines Arrays ['a', 'b'] wie folgt aus
'a' und 'b' enthältArray.prototype, die vom Array-Konstruktor bereitgestellten EigenschaftenObject.prototype, die vom Object-Konstruktor bereitgestellten Eigenschaftennull (das Ende der Kette, also kein wirkliches Mitglied davon)Sie können die Kette über Object.getPrototypeOf() untersuchen
> var arr = ['a', 'b'];
> var p = Object.getPrototypeOf;
> p(arr) === Array.prototype
true
> p(p(arr)) === Object.prototype
true
> p(p(p(arr)))
null
Eigenschaften in "früheren" Objekten überschreiben Eigenschaften in "späteren" Objekten. Zum Beispiel stellt Array.prototype eine Array-spezifische Version der toString()-Methode bereit, die Object.prototype.toString() überschreibt.
> var arr = ['a', 'b'];
> Object.getOwnPropertyNames(Array.prototype)
[ 'toString', 'join', 'pop', ··· ]
> arr.toString()
'a,b'
Wenn Sie den Methodenaufruf arr.toString() betrachten, sehen Sie, dass er tatsächlich zwei Schritte ausführt
arr wird der Wert der ersten Eigenschaft mit dem Namen toString abgerufen.this wird auf den *Empfänger* arr der Methodenaufrufung gesetzt.Sie können die beiden Schritte explizit machen, indem Sie die call()-Methode von Funktionen verwenden
> var func = arr.toString; // dispatch
> func.call(arr) // direct call, providing a value for `this`
'a,b'
Es gibt zwei Möglichkeiten, direkte Methodenaufrufe in JavaScript durchzuführen
Function.prototype.call(thisValue, arg0?, arg1?, ···)Function.prototype.apply(thisValue, argArray)Sowohl die Methode call als auch die Methode apply werden auf Funktionen aufgerufen. Sie unterscheiden sich von normalen Funktionsaufrufen dadurch, dass Sie einen Wert für this angeben. call stellt die Argumente des Methodenaufrufs über einzelne Parameter bereit, apply stellt sie über ein Array bereit.
Bei einem abgeglichenen Methodenaufruf spielt der Empfänger zwei Rollen: Er wird verwendet, um die Methode zu finden, und er ist ein impliziter Parameter. Ein Problem mit der ersten Rolle ist, dass eine Methode in der Prototypenkette eines Objekts vorhanden sein muss, wenn Sie sie aufrufen möchten. Bei einem direkten Methodenaufruf kann die Methode von überall kommen. Das erlaubt Ihnen, eine Methode von einem anderen Objekt zu leihen. Zum Beispiel können Sie Object.prototype.toString leihen und so die ursprüngliche, nicht überschriebene Implementierung von toString auf ein Array arr anwenden
> const arr = ['a','b','c'];
> Object.prototype.toString.call(arr)
'[object Array]'
Die Array-Version von toString() erzeugt ein anderes Ergebnis
> arr.toString() // dispatched
'a,b,c'
> Array.prototype.toString.call(arr); // direct
'a,b,c'
Methoden, die mit einer Vielzahl von Objekten arbeiten (nicht nur mit Instanzen "ihrer" Konstruktoren), werden als *generisch* bezeichnet. *Speaking JavaScript* hat eine Liste aller generischen Methoden. Die Liste enthält die meisten Array-Methoden und alle Methoden von Object.prototype (die mit allen Objekten funktionieren müssen und daher implizit generisch sind).
Dieser Abschnitt behandelt Anwendungsfälle für direkte Methodenaufrufe. Jedes Mal beschreibe ich zuerst den Anwendungsfall in ES5 und dann, wie er sich mit ES6 ändert (wo Sie selten direkte Methodenaufrufe benötigen werden).
Einige Funktionen akzeptieren mehrere Werte, aber nur einen Wert pro Parameter. Was, wenn Sie die Werte über ein Array übergeben möchten?
Zum Beispiel lässt push() Sie mehrere Werte destruktiv an ein Array anhängen
> var arr = ['a', 'b'];
> arr.push('c', 'd')
4
> arr
[ 'a', 'b', 'c', 'd' ]
Aber Sie können nicht ein ganzes Array destruktiv anhängen. Sie können diese Einschränkung umgehen, indem Sie apply() verwenden
> var arr = ['a', 'b'];
> Array.prototype.push.apply(arr, ['c', 'd'])
4
> arr
[ 'a', 'b', 'c', 'd' ]
Ähnlich funktionieren Math.max() und Math.min() nur für einzelne Werte
> Math.max(-1, 7, 2)
7
Mit apply() können Sie sie für Arrays verwenden
> Math.max.apply(null, [-1, 7, 2])
7
...) ersetzt apply() größtenteils Einen direkten Methodenaufruf über apply() nur deshalb zu tätigen, weil man ein Array in Argumente umwandeln möchte, ist umständlich, weshalb ECMAScript 6 den Spread-Operator (...) dafür hat. Er bietet diese Funktionalität sogar bei abgeglichenen Methodenaufrufen.
> Math.max(...[-1, 7, 2])
7
Ein weiteres Beispiel
> const arr = ['a', 'b'];
> arr.push(...['c', 'd'])
4
> arr
[ 'a', 'b', 'c', 'd' ]
Als Bonus funktioniert Spread auch mit dem new-Operator
> new Date(...[2011, 11, 24])
Sat Dec 24 2011 00:00:00 GMT+0100 (CET)
Beachten Sie, dass apply() nicht mit new verwendet werden kann – die obige Leistung kann in ECMAScript 5 nur über einen komplizierten Workaround erreicht werden.
Einige Objekte in JavaScript sind *Array-ähnlich*, sie sind fast Arrays, haben aber keine der Array-Methoden. Schauen wir uns zwei Beispiele an.
Erstens ist die spezielle Variable arguments von Funktionen Array-ähnlich. Sie hat eine length und indizierten Zugriff auf Elemente.
> var args = function () { return arguments }('a', 'b');
> args.length
2
> args[0]
'a'
Aber arguments ist keine Instanz von Array und hat nicht die Methode map().
> args instanceof Array
false
> args.map
undefined
Zweitens gibt die DOM-Methode document.querySelectorAll() eine Instanz von NodeList zurück.
> document.querySelectorAll('a[href]') instanceof NodeList
true
> document.querySelectorAll('a[href]').map // no Array methods!
undefined
Für viele komplexe Operationen müssen Sie daher zunächst Array-ähnliche Objekte in Arrays konvertieren. Dies geschieht über Array.prototype.slice(). Diese Methode kopiert die Elemente ihres Empfängers in ein neues Array
> var arr = ['a', 'b'];
> arr.slice()
[ 'a', 'b' ]
> arr.slice() === arr
false
Wenn Sie slice() direkt aufrufen, können Sie eine NodeList in ein Array konvertieren
var domLinks = document.querySelectorAll('a[href]');
var links = Array.prototype.slice.call(domLinks);
links.map(function (link) {
return link.href;
});
Und Sie können arguments in ein Array konvertieren
function format(pattern) {
// params start at arguments[1], skipping `pattern`
var params = Array.prototype.slice.call(arguments, 1);
return params;
}
console.log(format('a', 'b', 'c')); // ['b', 'c']
Einerseits hat ECMAScript 6 Array.from(), eine einfachere Möglichkeit, Array-ähnliche Objekte in Arrays zu konvertieren
const domLinks = document.querySelectorAll('a[href]');
const links = Array.from(domLinks);
links.map(link => link.href);
Andererseits werden Sie die Array-ähnlichen arguments nicht mehr benötigen, da ECMAScript 6 *Rest-Parameter* (deklariert durch drei Punkte) hat
function format(pattern, ...params) {
return params;
}
console.log(format('a', 'b', 'c')); // ['b', 'c']
hasOwnProperty() sicher verwenden obj.hasOwnProperty('prop') sagt Ihnen, ob obj die *eigene* (nicht geerbte) Eigenschaft prop hat.
> var obj = { prop: 123 };
> obj.hasOwnProperty('prop')
true
> 'toString' in obj // inherited
true
> obj.hasOwnProperty('toString') // own
false
Das Aufrufen von hasOwnProperty über Dispatch kann jedoch aufhören, ordnungsgemäß zu funktionieren, wenn Object.prototype.hasOwnProperty überschrieben wird.
> var obj1 = { hasOwnProperty: 123 };
> obj1.hasOwnProperty('toString')
TypeError: Property 'hasOwnProperty' is not a function
hasOwnProperty ist möglicherweise auch nicht über Dispatch verfügbar, wenn Object.prototype nicht in der Prototypenkette eines Objekts enthalten ist.
> var obj2 = Object.create(null);
> obj2.hasOwnProperty('toString')
TypeError: Object has no method 'hasOwnProperty'
In beiden Fällen ist die Lösung ein direkter Aufruf von hasOwnProperty
> var obj1 = { hasOwnProperty: 123 };
> Object.prototype.hasOwnProperty.call(obj1, 'hasOwnProperty')
true
> var obj2 = Object.create(null);
> Object.prototype.hasOwnProperty.call(obj2, 'toString')
false
hasOwnProperty() hasOwnProperty() wird hauptsächlich verwendet, um Maps über Objekte zu implementieren. Glücklicherweise hat ECMAScript 6 eine eingebaute Map-Datenstruktur, was bedeutet, dass Sie hasOwnProperty() seltener benötigen werden.
Object.prototype und Array.prototype Sie können auf die Methoden von Object.prototype über ein leeres Objektliteral (dessen Prototyp Object.prototype ist) zugreifen. Zum Beispiel sind die folgenden beiden direkten Methodenaufrufe äquivalent
Object.prototype.hasOwnProperty.call(obj, 'propKey')
{}.hasOwnProperty.call(obj, 'propKey')
Der gleiche Trick funktioniert auch für Array.prototype
Array.prototype.slice.call(arguments)
[].slice.call(arguments)
Dieses Muster ist recht populär geworden. Es spiegelt nicht die Absicht des Autors so klar wider wie die längere Version, ist aber viel weniger umständlich. Geschwindigkeitstechnisch gibt es kaum einen Unterschied zwischen den beiden Versionen.
name von Funktionen Die Eigenschaft name einer Funktion enthält den Namen der Funktion
> function foo() {}
> foo.name
'foo'
Diese Eigenschaft ist nützlich für das Debugging (ihr Wert erscheint in Stack-Traces) und einige Metaprogrammierungsaufgaben (Auswahl einer Funktion nach Namen usw.).
Vor ES6 wurde diese Eigenschaft bereits von den meisten Engines unterstützt. Mit ES6 wird sie Teil des Sprachstandards und wird häufig automatisch ausgefüllt.
Die folgenden Abschnitte beschreiben, wie name für verschiedene Programmierkonstrukte automatisch eingerichtet wird.
Funktionen erhalten Namen, wenn sie über Variablendeklarationen erstellt werden
let func1 = function () {};
console.log(func1.name); // func1
const func2 = function () {};
console.log(func2.name); // func2
var func3 = function () {};
console.log(func3.name); // func3
Aber auch bei einer normalen Zuweisung wird name korrekt eingerichtet
let func4;
func4 = function () {};
console.log(func4.name); // func4
var func5;
func5 = function () {};
console.log(func5.name); // func5
Im Hinblick auf Namen sind Pfeilfunktionen wie anonyme Funktionsausdrücke
const func = () => {};
console.log(func.name); // func
Von nun an können Sie, wann immer Sie einen anonymen Funktionsausdruck sehen, davon ausgehen, dass eine Pfeilfunktion genauso funktioniert.
Wenn eine Funktion ein Standardwert ist, erhält sie ihren Namen von ihrer Variablen oder ihrem Parameter
let [func1 = function () {}] = [];
console.log(func1.name); // func1
let { f2: func2 = function () {} } = {};
console.log(func2.name); // func2
function g(func3 = function () {}) {
return func3.name;
}
console.log(g()); // func3
Funktionsdeklarationen und Funktionsausdrücke sind Funktionsdefinitionen. Dieses Szenario wird seit langem unterstützt: Eine Funktiondefinition mit einem Namen gibt diesen an die Eigenschaft name weiter.
Zum Beispiel eine Funktionsdeklaration
function foo() {}
console.log(foo.name); // foo
Der Name eines benannten Funktionsausdrucks richtet ebenfalls die name-Eigenschaft ein.
const bar = function baz() {};
console.log(bar.name); // baz
Da der Name baz des Funktionsausdrucks zuerst kommt, hat er Vorrang vor anderen Namen (z. B. dem Namen bar, der über die Variablendeklaration bereitgestellt wird).
Wie in ES5 ist der Name eines Funktionsausdrucks jedoch nur eine Variable innerhalb des Funktionsausdrucks
const bar = function baz() {
console.log(baz.name); // baz
};
bar();
console.log(baz); // ReferenceError
Wenn eine Funktion der Wert einer Eigenschaft ist, erhält sie ihren Namen von dieser Eigenschaft. Es spielt keine Rolle, ob dies über eine Methodendefinition (Zeile A), eine herkömmliche Eigenschaftsdefinition (Zeile B), eine Eigenschaftsdefinition mit einem berechneten Eigenschaftsschlüssel (Zeile C) oder eine Kurzschreibweise für den Eigenschaftswert (Zeile D) geschieht.
function func() {}
let obj = {
m1() {}, // (A)
m2: function () {}, // (B)
['m' + '3']: function () {}, // (C)
func, // (D)
};
console.log(obj.m1.name); // m1
console.log(obj.m2.name); // m2
console.log(obj.m3.name); // m3
console.log(obj.func.name); // func
Die Namen von Gettern werden mit 'get', die Namen von Settern mit 'set' präfigiert
let obj = {
get foo() {},
set bar(value) {},
};
let getter = Object.getOwnPropertyDescriptor(obj, 'foo').get;
console.log(getter.name); // 'get foo'
let setter = Object.getOwnPropertyDescriptor(obj, 'bar').set;
console.log(setter.name); // 'set bar'
Die Benennung von Methoden in Klassendefinitionen ähnelt der von Objektliteralen
class C {
m1() {}
['m' + '2']() {} // computed property key
static classMethod() {}
}
console.log(C.prototype.m1.name); // m1
console.log(new C().m1.name); // m1
console.log(C.prototype.m2.name); // m2
console.log(C.classMethod.name); // classMethod
Getter und Setter haben wieder die Namenspräfixe 'get' bzw. 'set'
class C {
get foo() {}
set bar(value) {}
}
let getter = Object.getOwnPropertyDescriptor(C.prototype, 'foo').get;
console.log(getter.name); // 'get foo'
let setter = Object.getOwnPropertyDescriptor(C.prototype, 'bar').set;
console.log(setter.name); // 'set bar'
In ES6 kann der Schlüssel einer Methode ein Symbol sein. Die Eigenschaft name einer solchen Methode ist immer noch ein String
'').const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
console.log(obj[key1].name); // '[description]'
console.log(obj[key2].name); // ''
Denken Sie daran, dass Klassendefinitionen Funktionen erstellen. Diese Funktionen haben ebenfalls ihre Eigenschaft name korrekt eingerichtet
class Foo {}
console.log(Foo.name); // Foo
const Bar = class {};
console.log(Bar.name); // Bar
Alle folgenden Anweisungen setzen name auf 'default'
export default function () {}
export default (function () {});
export default class {}
export default (class {});
export default () => {};
new Function() erzeugt Funktionen, deren name 'anonymous' ist. Ein Webkit-Bug beschreibt, warum das im Web notwendig ist.func.bind() erzeugt eine Funktion, deren name 'bound '+func.name ist function foo(x) {
return x
}
const bound = foo.bind(undefined, 123);
console.log(bound.name); // 'bound foo'
Funktionsnamen werden immer während der Erstellung zugewiesen und nie später geändert. Das heißt, JavaScript-Engines erkennen die zuvor genannten Muster und erstellen Funktionen, die mit den korrekten Namen beginnen. Der folgende Code zeigt, dass der Name der von functionFactory() erstellten Funktion in Zeile A zugewiesen und nicht durch die Deklaration in Zeile B geändert wird.
function functionFactory() {
return function () {}; // (A)
}
const foo = functionFactory(); // (B)
console.log(foo.name.length); // 0 (anonymous)
Man könnte theoretisch bei jeder Zuweisung prüfen, ob die rechte Seite zu einer Funktion ausgewertet wird und ob diese Funktion noch keinen Namen hat. Dies würde jedoch zu einer erheblichen Leistungseinbuße führen.
Funktionsnamen unterliegen der Minifizierung, was bedeutet, dass sie in minifiziertem Code normalerweise geändert werden. Je nachdem, was Sie tun möchten, müssen Sie Funktionsnamen möglicherweise über Strings (die nicht minifiziert werden) verwalten oder Ihren Minifier anweisen, welche Namen nicht minifiziert werden sollen.
Dies sind die Attribute der Eigenschaft name
> let func = function () {}
> Object.getOwnPropertyDescriptor(func, 'name')
{ value: 'func',
writable: false,
enumerable: false,
configurable: true }
Da die Eigenschaft nicht beschreibbar ist, können Sie ihren Wert nicht durch Zuweisung ändern
> func.name = 'foo';
> func.name
'func'
Die Eigenschaft ist jedoch konfigurierbar, was bedeutet, dass Sie sie durch Neudefinition ändern können
> Object.defineProperty(func, 'name', {value: 'foo', configurable: true});
> func.name
'foo'
Wenn die Eigenschaft name bereits vorhanden ist, können Sie die Deskriptoreigenschaft configurable weglassen, da fehlende Deskriptoreigenschaften bedeuten, dass die entsprechenden Attribute nicht geändert werden.
Wenn die Eigenschaft name noch nicht vorhanden ist, stellt die Deskriptoreigenschaft configurable sicher, dass name konfigurierbar bleibt (die Standardattributwerte sind alle false oder undefined).
name in der Spezifikation SetFunctionName() richtet die Eigenschaft name ein. Suchen Sie nach ihrem Namen in der Spezifikation, um herauszufinden, wo das geschieht.'get' und 'set')Function.prototype.bind() (Präfix 'bound')name können Sie in ihren Laufzeitschemata sehen.SetFunctionName() eingerichtet. Diese Operation wird für anonyme Funktionsausdrücke nicht aufgerufen.SetFunctionName() wird nicht aufgerufen).new aufgerufen wurde? ES6 hat ein neues Protokoll für Unterklassenbildung, das im Kapitel über Klassen erklärt wird. Teil dieses Protokolls ist das Meta-Property new.target, das auf das erste Element in einer Kette von Konstruktoraufrufen verweist (ähnlich wie this in einer Kette für Supermethodenaufrufe). Es ist undefined, wenn kein Konstruktoraufruf stattfindet. Wir können dies verwenden, um zu erzwingen, dass eine Funktion über new aufgerufen werden muss oder nicht. Dies ist ein Beispiel für Letzteres
function realFunction() {
if (new.target !== undefined) {
throw new Error('Can’t be invoked via `new`');
}
···
}
In ES5 wurde dies üblicherweise so überprüft
function realFunction() {
"use strict";
if (this !== undefined) {
throw new Error('Can’t be invoked via `new`');
}
···
}