12. Aufrufbare Entitäten in ECMAScript 6
Inhaltsverzeichnis
Bitte unterstützen Sie dieses Buch: kaufen Sie es (PDF, EPUB, MOBI) oder spenden Sie
(Werbung, bitte nicht blockieren.)

12. Aufrufbare Entitäten in ECMAScript 6

Dieses Kapitel gibt Ratschläge, wie man aufrufbare Entitäten (mittels Funktionsaufrufen, Methodenaufrufen usw.) in ES6 korrekt verwendet.



12.1 Überblick

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'

12.2 Arten von Aufrufen in ES6

Einige Aufrufe können überall getätigt werden, andere sind an bestimmte Orte gebunden.

12.2.1 Aufrufe, die überall getätigt werden können

Drei Arten von Aufrufen können in ES6 überall getätigt werden

12.2.2 Aufrufe über 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

12.2.3 Nicht-Methoden-Funktionen versus Methoden

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

12.3 Empfehlungen für die Verwendung aufrufbarer Entitäten

Dieser Abschnitt gibt Tipps zur Verwendung aufrufbarer Entitäten: Wann welche Entität am besten verwendet wird; usw.

12.3.1 Pfeilfunktionen als Callbacks bevorzugen

Als Callbacks haben Pfeilfunktionen zwei Vorteile gegenüber traditionellen Funktionen

12.3.1.1 Problem: 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.

12.3.1.2 Lösung 1: API ändern

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.

12.3.1.3 Lösung 2: auf den Wert von 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');
});

12.3.2 Funktionsdeklarationen als eigenständige Funktionen bevorzugen

Als eigenständige Funktionen (im Gegensatz zu Callbacks) bevorzuge ich Funktionsdeklarationen

function foo(arg1, arg2) {
    ···
}

Die Vorteile sind

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) => {
    ···
};

12.3.3 Methodendefinitionen für Methoden bevorzugen

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().

12.3.4 Methoden versus Callbacks

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.

12.3.4.1 Ein Objekt, dessen Eigenschaften Methoden sind

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 **).

12.3.4.2 Ein Objekt, dessen Eigenschaften Callbacks sind

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.

12.3.5 IIFEs in ES6 vermeiden

Dieser Abschnitt gibt Tipps zur Vermeidung von IIFEs in ES6.

12.3.5.1 Eine IIFE durch einen Block ersetzen

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
12.3.5.2 Eine IIFE durch ein Modul ersetzen

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);
12.3.5.3 Sofort aufgerufene Pfeilfunktionen

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.

12.3.6 Klassen als Konstruktoren verwenden

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.

12.4 Aufrufbare ES6-Entitäten im Detail

Dieser Abschnitt beginnt mit einem Spickzettel, bevor jede ES6-aufrufbare Entität im Detail beschrieben wird.

12.4.1 Spickzettel: aufrufbare Entitäten

12.4.1.1 Das Verhalten und die Struktur aufrufbarer Entitäten

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

Legende – Fußnoten

Was ist mit Generatorfunktionen und -methoden? Diese funktionieren wie ihre nicht-generatorischen Gegenstücke, mit zwei Ausnahmen

12.4.1.2 Die Regeln für 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

12.4.2 Traditionelle Funktionen

Dies sind die Funktionen, die Sie aus ES5 kennen. Es gibt zwei Möglichkeiten, sie zu erstellen

Regeln für this

12.4.3 Generatorfunktionen

Generatorfunktionen werden im Kapitel über Generatoren erläutert. Ihre Syntax ähnelt traditionellen Funktionen, aber sie haben ein zusätzliches Sternchen

Die Regeln für this sind wie folgt. Beachten Sie, dass sich this niemals auf das Generatorobjekt bezieht.

12.4.4 Methodendefinitionen

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

Innerhalb von Klassendefinitionen sind Methoden mit dem Namen constructor besonders, wie später in diesem Kapitel erläutert wird.

12.4.5 Generator-Methodendefinitionen

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

12.4.6 Pfeilfunktionen

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)

Regeln

12.4.7 Klassen

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

12.5 Abgeglichene und direkte Methodenaufrufe in ES5 und ES6

Es gibt zwei Möglichkeiten, Methoden in JavaScript aufzurufen

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.

12.5.1 Hintergrund: Prototypketten

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

  1. Die Instanz, die die Elemente 'a' und 'b' enthält
  2. Array.prototype, die vom Array-Konstruktor bereitgestellten Eigenschaften
  3. Object.prototype, die vom Object-Konstruktor bereitgestellten Eigenschaften
  4. null (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'

12.5.2 Abgeglichene Methodenaufrufe

Wenn Sie den Methodenaufruf arr.toString() betrachten, sehen Sie, dass er tatsächlich zwei Schritte ausführt

  1. Abgleich: In der Prototypenkette von arr wird der Wert der ersten Eigenschaft mit dem Namen toString abgerufen.
  2. Aufruf: Der Wert wird aufgerufen und der implizite Parameter 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'

12.5.3 Direkte Methodenaufrufe

Es gibt zwei Möglichkeiten, direkte Methodenaufrufe in JavaScript durchzuführen

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).

12.5.4 Anwendungsfälle für direkte Methodenaufrufe

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).

12.5.4.1 ES5: Parameter an eine Methode über ein Array übergeben

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
12.5.4.2 ES6: Der Spread-Operator (...) 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.

12.5.4.3 ES5: Ein Array-ähnliches Objekt in ein Array konvertieren

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']
12.5.4.4 ES6: Array-ähnliche Objekte sind weniger belastend

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']
12.5.4.5 ES5: 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
12.5.4.6 ES6: Weniger Bedarf an 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.

12.5.5 Abkürzungen für 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.

12.6 Die Eigenschaft 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.

12.6.1 Konstrukte, die Funktionen Namen geben

Die folgenden Abschnitte beschreiben, wie name für verschiedene Programmierkonstrukte automatisch eingerichtet wird.

12.6.1.1 Variablendeklarationen und Zuweisungen

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.

12.6.1.2 Standardwerte

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
12.6.1.3 Benannte Funktionsdefinitionen

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
12.6.1.4 Methoden in Objektliteralen

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'
12.6.1.5 Methoden in Klassendefinitionen

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'
12.6.1.6 Methoden, deren Schlüssel Symbole sind

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); // ''
12.6.1.7 Klassendefinitionen

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
12.6.1.8 Standardexporte

Alle folgenden Anweisungen setzen name auf 'default'

export default function () {}
export default (function () {});

export default class {}
export default (class {});

export default () => {};
12.6.1.9 Andere Programmierkonstrukte

12.6.2 Vorbehalte

12.6.2.1 Vorbehalt: Der Name einer Funktion wird immer bei der Erstellung zugewiesen

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.

12.6.2.2 Vorbehalt: Minifizierung

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.

12.6.3 Ändern der Namen von Funktionen

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).

12.6.4 Die Funktionseigenschaft name in der Spezifikation

12.7 FAQ: aufrufbare Entitäten

12.7.1 Wie stelle ich fest, ob eine Funktion über 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`');
    }
    ···
}
Weiter: 13. Pfeilfunktionen