Warum benannte Funktionsausdrücke verwenden?

92

Wir haben zwei verschiedene Möglichkeiten, um Funktionsausdrücke in JavaScript auszuführen:

Benannter Funktionsausdruck (NFE) :

var boo = function boo () {
  alert(1);
};

Anonymer Funktionsausdruck :

var boo = function () {
  alert(1);
};

Und beide können mit angerufen werden boo();. Ich kann wirklich nicht verstehen, warum / wann ich anonyme Funktionen verwenden sollte und wann ich benannte Funktionsausdrücke verwenden sollte. Welchen Unterschied gibt es zwischen ihnen?

Afshin Mehrabani
quelle

Antworten:

85

Im Fall des anonymen Funktionsausdrucks ist die Funktion anonym  - buchstäblich hat sie keinen Namen. Die Variable, der Sie sie zuweisen, hat einen Namen, die Funktion jedoch nicht. (Update: Das war durch ES5 der Fall. Ab ES2015 [auch bekannt als ES6] erhält eine mit einem anonymen Ausdruck erstellte Funktion häufig einen wahren Namen [aber keine automatische Kennung], lesen Sie weiter ...)

Namen sind nützlich. Namen können in Stapelspuren, Aufrufstapeln, Listen von Haltepunkten usw. angezeigt werden. Namen sind eine gute Sache ™.

(Früher mussten Sie sich in älteren Versionen von IE [IE8 und darunter] vor benannten Funktionsausdrücken hüten, da diese fälschlicherweise zwei vollständig getrennte Funktionsobjekte zu zwei völlig unterschiedlichen Zeiten erstellt haben [mehr in meinem Blog-Artikel Double take ]. Wenn nötig Unterstützung IE8 [!!], ist es wahrscheinlich am besten mit anonymen Funktion Ausdrücke oder Funktion halten Erklärungen , aber vermeiden sie Funktion Ausdrücke genannt.)

Eine wichtige Sache bei einem benannten Funktionsausdruck ist, dass er einen In-Scope-Bezeichner mit diesem Namen für die Funktion innerhalb des Funktionskörpers erstellt:

var x = function example() {
    console.log(typeof example); // "function"
};
x();
console.log(typeof example);     // "undefined"

Ab ES2015 erstellen jedoch viele "anonyme" Funktionsausdrücke Funktionen mit Namen, und dies wurde von verschiedenen modernen JavaScript-Engines vorweggenommen, die ziemlich klug darin waren, Namen aus dem Kontext abzuleiten. In ES2015 führt Ihr anonymer Funktionsausdruck zu einer Funktion mit dem Namen boo. Selbst mit ES2015 + -Semantik wird die automatische Kennung jedoch nicht erstellt:

var obj = {
    x: function() {
       console.log(typeof x);   // "undefined"
       console.log(obj.x.name); // "x"
    },
    y: function y() {
       console.log(typeof y);   // "function"
       console.log(obj.y.name); // "y"
    }
};
obj.x();
obj.y();

Die Zuweisung des Funktionsnamens erfolgt mit der abstrakten Operation SetFunctionName, die in verschiedenen Operationen in der Spezifikation verwendet wird.

Die Kurzversion ist im Grunde immer dann, wenn ein anonymer Funktionsausdruck auf der rechten Seite von etwas wie einer Zuweisung oder Initialisierung erscheint, wie:

var boo = function() { /*...*/ };

(oder es könnte sein letoder consteher als var) oder

var obj = {
    boo: function() { /*...*/ }
};

oder

doSomething({
    boo: function() { /*...*/ }
});

(Die letzten beiden sind wirklich dasselbe) , die resultierende Funktion hat einen Namen ( booin den Beispielen).

Es gibt eine wichtige und absichtliche Ausnahme: Zuweisen einer Eigenschaft zu einem vorhandenen Objekt:

obj.boo = function() { /*...*/ }; // <== Does not get a name

Dies war auf Bedenken hinsichtlich Informationslecks zurückzuführen, die beim Hinzufügen der neuen Funktion aufgetreten waren. Details in meiner Antwort auf eine andere Frage hier .

TJ Crowder
quelle
1
Es ist erwähnenswert, dass es mindestens zwei Stellen gibt, an denen die Verwendung von NFEs noch konkrete Vorteile bietet: Erstens für Funktionen, die als Konstruktoren über den newOperator verwendet werden sollen (die Angabe aller dieser Funktionsnamen macht die .constructorEigenschaft beim Debuggen nützlicher, um herauszufinden, was zum Teufel ist Ein Objekt ist eine Instanz von) und für Funktionsliterale, die direkt an eine Funktion übergeben werden, ohne zuvor einer Eigenschaft oder Variablen (z setTimeout(function () {/*do stuff*/});. B. ) zugewiesen zu werden . Sogar Chrome zeigt diese an, es (anonymous function)sei denn, Sie helfen mit, indem Sie sie benennen.
Mark Amery
4
@ MarkAmery: " Ist das noch wahr? Ich ... habe versucht, STRG-F für diese Regeln zu verwenden und konnte sie nicht finden." Oh ja. :-) Es ist in der gesamten Spezifikation verstreut, anstatt sich an einem Ort zu befinden, der eine Reihe von Regeln definiert. Suchen Sie einfach nach "setFunctionName". Ich habe oben eine kleine Untergruppe von Links hinzugefügt, die derzeit an ~ 29 verschiedenen Stellen angezeigt werden. Ich wäre nur leicht überrascht, wenn Ihr setTimeoutBeispiel den Namen nicht aus dem formalen Argument übernommen hätte, für das setTimeoutes deklariert wurde , wenn es einen hatte. :-) Aber ja, NFEs sind definitiv nützlich, wenn Sie wissen, dass Sie nicht mit alten Browsern zu tun haben, die einen Hash daraus machen.
TJ Crowder
23

Benennungsfunktionen sind nützlich, wenn sie sich selbst referenzieren müssen (z. B. für rekursive Aufrufe). Wenn Sie einen Literalfunktionsausdruck als Argument direkt an eine andere Funktion übergeben, kann dieser Funktionsausdruck im strengen ES5-Modus nur dann direkt auf sich selbst verweisen, wenn er benannt ist.

Betrachten Sie beispielsweise diesen Code:

setTimeout(function sayMoo() {
    alert('MOO');
    setTimeout(sayMoo, 1000);
}, 1000);

Es wäre unmöglich, diesen Code so sauber zu schreiben, wenn der übergebene Funktionsausdruck setTimeoutanonym wäre. Wir müssten es stattdessen vor dem setTimeoutAufruf einer Variablen zuweisen . Auf diese Weise ist ein benannter Funktionsausdruck etwas kürzer und übersichtlicher.

Es war historisch möglich, Code wie diesen sogar mit einem anonymen Funktionsausdruck zu schreiben, indem arguments.callee...

setTimeout(function () {
    alert('MOO');
    setTimeout(arguments.callee, 1000);
}, 1000);

... ist aber arguments.calleeveraltet und im strengen ES5-Modus völlig verboten. Daher rät MDN:

Vermeiden Sie die Verwendung arguments.callee()von entweder geben Funktion Ausdrücke einen Namen oder eine Funktionsdeklaration verwenden , bei denen eine Funktion selbst aufrufen müssen.

(Hervorhebung von mir)

Mark Amery
quelle
3

Wenn eine Funktion als Funktionsausdruck angegeben ist, kann ihr ein Name zugewiesen werden.

Es ist nur innerhalb der Funktion verfügbar (außer IE8-).

var f = function sayHi(name) {
  alert( sayHi ); // Inside the function you can see the function code
};

alert( sayHi ); // (Error: undefined variable 'sayHi')

Dieser Name ist für einen zuverlässigen rekursiven Funktionsaufruf vorgesehen, auch wenn er in eine andere Variable geschrieben wird.

Darüber hinaus kann der NFE-Name (Named Function Expression) mit der folgenden Object.defineProperty(...)Methode überschrieben werden:

var test = function sayHi(name) {
  Object.defineProperty(test, 'name', { value: 'foo', configurable: true });
  alert( test.name ); // foo
};

test();

Hinweis: Mit der Funktionsdeklaration ist dies nicht möglich. Dieser "spezielle" interne Funktionsname wird nur in der Syntax des Funktionsausdrucks angegeben.

römisch
quelle
2

Sie sollten immer benannte Funktionsausdrücke verwenden, deshalb:

  1. Sie können den Namen dieser Funktion verwenden, wenn Sie eine Rekursion benötigen.

  2. Anonyme Funktionen helfen beim Debuggen nicht, da der Name der Funktion, die Probleme verursacht, nicht angezeigt wird.

  3. Wenn Sie eine Funktion nicht benennen, ist es später schwieriger zu verstehen, was sie tut. Wenn Sie ihm einen Namen geben, ist das Verständnis einfacher.

var foo = function bar() {
    //some code...
};

foo();
bar(); // Error!

Da hier beispielsweise die Namensleiste in einem Funktionsausdruck verwendet wird, wird sie im äußeren Bereich nicht deklariert. Bei benannten Funktionsausdrücken wird der Name des Funktionsausdrucks in einen eigenen Bereich eingeschlossen.

Antero Ukkonen
quelle
1

Die Verwendung benannter Funktionsausdrücke ist besser, wenn Sie auf die betreffende Funktion verweisen möchten, ohne sich auf veraltete Funktionen wie z arguments.callee.

Sudhir Bastakoti
quelle
3
Das ist eher ein Kommentar als eine Antwort. Vielleicht wäre die Ausarbeitung von Vorteil
vsync