Frage var funktionName = funktion () {} vs. funktion funktionName () {}


Ich habe kürzlich begonnen, den JavaScript-Code von jemand anderem zu pflegen. Ich behebe Fehler, füge Funktionen hinzu und versuche auch, den Code aufzuräumen und ihn konsistenter zu machen.

Der vorherige Entwickler verwendet zwei Möglichkeiten, Funktionen zu deklarieren, und ich kann nicht herausfinden, ob es einen Grund dafür gibt oder nicht.

Die zwei Möglichkeiten sind:

var functionOne = function() {
    // Some code
};
function functionTwo() {
    // Some code
}

Was sind die Gründe für die Verwendung dieser zwei verschiedenen Methoden und was sind die Vor- und Nachteile von jedem? Gibt es etwas, das mit einer Methode gemacht werden kann, die mit der anderen nicht möglich ist?


6052
2017-12-03 11:31


Ursprung


Antworten:


Der Unterschied ist der functionOne ist ein Funktionsausdruck und wird nur dann definiert, wenn diese Zeile erreicht ist functionTwo ist eine Funktionsdeklaration und wird definiert, sobald die umgebende Funktion oder das Skript ausgeführt wird (wegen hissen).

Zum Beispiel ein Funktionsausdruck:

// TypeError: functionOne is not a function
functionOne();

var functionOne = function() {
  console.log("Hello!");
};

Und eine Funktionsdeklaration:

// Outputs: "Hello!"
functionTwo();

function functionTwo() {
  console.log("Hello!");
}

Dies bedeutet auch, dass Sie Funktionen nicht unter Verwendung von Funktionsdeklarationen definieren können:

if (test) {
   // Error or misbehavior
   function functionThree() { doSomething(); }
}

Das Obige definiert tatsächlich functionThree unabhängig von testder Wert - es sei denn use strict ist in der Tat, in welchem ​​Fall es einfach einen Fehler wirft.


4489
2017-12-03 11:37



Zuerst möchte ich Greg korrigieren: function abc(){} ist auch begrenzt - der Name abc ist in dem Bereich definiert, in dem diese Definition auftritt. Beispiel:

function xyz(){
  function abc(){};
  // abc is defined here...
}
// ...but not here

Zweitens ist es möglich, beide Stile zu kombinieren:

var xyz = function abc(){};

xyz wird wie üblich definiert, abc ist in allen Browsern nicht definiert, aber Internet Explorer - verlassen Sie sich nicht darauf, dass es definiert wird. Aber es wird in seinem Körper definiert werden:

var xyz = function abc(){
  // xyz is visible here
  // abc is visible here
}
// xyz is visible here
// abc is undefined here

Wenn Sie Funktionen in allen Browsern als Alias ​​verwenden möchten, verwenden Sie diese Art der Deklaration:

function abc(){};
var xyz = abc;

In diesem Fall beides xyz und abc sind Aliase des gleichen Objekts:

console.log(xyz === abc); // prints "true"

Ein zwingender Grund, den kombinierten Stil zu verwenden, ist das Attribut "name" von Funktionsobjekten (nicht vom Internet Explorer unterstützt). Grundsätzlich wenn Sie eine Funktion wie definieren

function abc(){};
console.log(abc.name); // prints "abc"

Der Name wird automatisch vergeben. Aber wenn Sie es so definieren

var abc = function(){};
console.log(abc.name); // prints ""

Der Name ist leer - wir haben eine anonyme Funktion erstellt und sie einer Variablen zugewiesen.

Ein weiterer guter Grund, den kombinierten Stil zu verwenden, besteht darin, einen kurzen internen Namen zu verwenden, um auf sich selbst zu verweisen und gleichzeitig einen langen, nicht konfliktbehafteten Namen für externe Benutzer bereitzustellen:

// Assume really.long.external.scoped is {}
really.long.external.scoped.name = function shortcut(n){
  // Let it call itself recursively:
  shortcut(n - 1);
  // ...
  // Let it pass itself as a callback:
  someFunction(shortcut);
  // ...
}

Im obigen Beispiel können wir dasselbe mit einem externen Namen machen, aber es wird zu unhandlich (und langsamer).

(Eine andere Möglichkeit, sich auf sich selbst zu beziehen, ist zu verwenden arguments.callee, die immer noch relativ lang ist und im strikten Modus nicht unterstützt wird.)

Im Grunde behandelt JavaScript beide Anweisungen unterschiedlich. Dies ist eine Funktionsdeklaration:

function abc(){}

abc hier ist überall im aktuellen Umfang definiert:

// We can call it here
abc(); // Works

// Yet, it is defined down there.
function abc(){}

// We can call it again
abc(); // Works

Auch er hisste durch ein return Erklärung:

// We can call it here
abc(); // Works
return;
function abc(){}

Dies ist ein Funktionsausdruck:

var xyz = function(){};

xyz hier ist ab dem Punkt der Zuordnung definiert:

// We can't call it here
xyz(); // UNDEFINED!!!

// Now it is defined
xyz = function(){}

// We can call it here
xyz(); // works

Funktionsdeklaration gegen Funktionsausdruck ist der wahre Grund dafür, dass Greg einen Unterschied zeigt.

Lustige Tatsache:

var xyz = function abc(){};
console.log(xyz.name); // Prints "abc"

Persönlich bevorzuge ich die Deklaration "Funktionsausdruck", weil ich auf diese Weise die Sichtbarkeit kontrollieren kann. Wenn ich die Funktion wie definieren

var abc = function(){};

Ich weiß, dass ich die Funktion lokal definiert habe. Wenn ich die Funktion wie definieren

abc = function(){};

Ich weiß, dass ich es global definiert habe, vorausgesetzt, ich habe es nicht definiert abc irgendwo in der Kette der Bereiche. Diese Art der Definition ist widerstandsfähig, selbst wenn sie im Inneren verwendet wird eval(). Während die Definition

function abc(){};

hängt vom Kontext ab und lässt Sie wahrscheinlich raten, wo es wirklich definiert ist, besonders im Fall von eval() - Die Antwort ist: Es hängt vom Browser ab.


1804
2017-12-03 17:43



Hier ist der Überblick über die Standardformulare, die Funktionen erstellen: (Ursprünglich für eine andere Frage geschrieben, aber angepasst, nachdem sie in die kanonische Frage gebracht wurde.)

Begriffe:

Die schnelle Liste:

  • Funktionsdeklaration

  • "Anonym" function Ausdruck (die trotz des Begriffs manchmal Funktionen mit Namen erstellen)

  • Genannt function Ausdruck

  • Accessor-Funktionsinitialisierung (ES5 +)

  • Pfeilfunktionsausdruck (ES2015 +) (die wie anonyme Funktionsausdrücke keinen expliziten Namen enthalten und dennoch Funktionen mit Namen erstellen können)

  • Methodendeklaration im Objektinitialisierer (ES2015 +)

  • Deklarationen von Konstruktoren und Methoden in class (ES2015 +)

Funktionsdeklaration

Die erste Form ist a Funktionsdeklaration, die so aussieht:

function x() {
    console.log('x');
}

Eine Funktionsdeklaration ist a Erklärung; Es ist keine Aussage oder Ausdruck. Daher folgst du es nicht mit ; (obwohl dies harmlos ist).

Eine Funktionsdeklaration wird verarbeitet, wenn die Ausführung in den Kontext eintritt, in dem sie erscheint. Vor Jeder Schritt-für-Schritt-Code wird ausgeführt. Die Funktion, die sie erstellt, erhält einen eigenen Namen (x im obigen Beispiel), und dieser Name wird in den Bereich gestellt, in dem die Deklaration erscheint.

Da es vor jedem Schritt-für-Schritt-Code im selben Kontext verarbeitet wird, können Sie Folgendes tun:

x(); // Works even though it's above the declaration
function x() {
    console.log('x');
}

Bis ES2015 wurde in der Spezifikation nicht behandelt, was eine JavaScript-Engine tun sollte, wenn Sie eine Funktionsdeklaration in eine Kontrollstruktur wie z try, if, switch, whileusw., so:

if (someCondition) {
    function foo() {    // <===== HERE THERE
    }                   // <===== BE DRAGONS
}

Und da sie verarbeitet werden Vor Schritt-für-Schritt-Code ausgeführt wird, ist es schwierig zu wissen, was zu tun ist, wenn sie in einer Kontrollstruktur sind.

Obwohl dies nicht getan wurde angegeben bis ES2015 war es ein zulässige Erweiterung Funktionsdeklarationen in Blöcken unterstützen. Leider (und unweigerlich) haben verschiedene Motoren verschiedene Dinge getan.

Ab ES2015 sagt die Spezifikation, was zu tun ist. In der Tat gibt es drei verschiedene Dinge zu tun:

  1. Wenn im lockeren Modus nicht In einem Webbrowser soll die JavaScript-Engine eine Sache machen
  2. Wenn sich die JavaScript-Engine in einem Webbrowser in einem lockeren Modus befindet, sollte sie etwas anderes tun
  3. Wenn in streng Modus (Browser oder nicht), die JavaScript-Engine soll noch etwas anderes tun

Die Regeln für die lockeren Modi sind schwierig, aber in streng Modus, Funktionsdeklarationen in Blöcken sind einfach: Sie sind lokal für den Block (sie haben Blockumfang, die auch in ES2015 neu ist), und sie werden an die Spitze des Blocks gehievt. Damit:

"use strict";
if (someCondition) {
    foo();               // Works just fine
    function foo() {
    }
}
console.log(typeof foo); // "undefined" (`foo` is not in scope here
                         // because it's not in the same block)

"Anonym" function Ausdruck

Die zweite gemeinsame Form heißt ein anonymer Funktionsausdruck:

var y = function () {
    console.log('y');
};

Wie alle Ausdrücke wird es ausgewertet, wenn es in der schrittweisen Ausführung des Codes erreicht wird.

In ES5 hat die erstellte Funktion keinen Namen (sie ist anonym). In ES2015 wird der Funktion, wenn möglich, ein Name zugewiesen, indem sie aus dem Kontext abgeleitet wird. Im obigen Beispiel wäre der Name y. Etwas Ähnliches wird gemacht, wenn die Funktion der Wert eines Eigenschaftsinitialisierers ist. (Details dazu, wann dies geschieht und nach welchen Regeln suchen Sie nach SetFunctionName in dem die Spezifikation- es erscheint überall der Ort.)

Genannt function Ausdruck

Die dritte Form ist a benannter Funktionsausdruck ("NFE"):

var z = function w() {
    console.log('zw')
};

Die erstellte Funktion hat einen eigenen Namen (w in diesem Fall). Wie bei allen Ausdrücken wird dies ausgewertet, wenn es in der schrittweisen Ausführung des Codes erreicht wird. Der Name der Funktion ist nicht hinzugefügt zu dem Bereich, in dem der Ausdruck erscheint; der Name ist im Umfang innerhalb der Funktion selbst:

var z = function w() {
    console.log(typeof w); // "function"
};
console.log(typeof w);     // "undefined"

Beachten Sie, dass NFEs häufig eine Fehlerquelle für JavaScript-Implementierungen waren. IE8 und früher behandeln zum Beispiel NFEs völlig falschErstellen von zwei verschiedenen Funktionen zu zwei verschiedenen Zeiten. Frühe Versionen von Safari hatten auch Probleme. Die gute Nachricht ist, dass aktuelle Versionen von Browsern (IE9 und höher, aktuelle Safari) diese Probleme nicht mehr haben. (Leider ist IE8 leider weiterhin weit verbreitet, und daher ist die Verwendung von NFEs mit Code für das Web im Allgemeinen immer noch problematisch.)

Accessor-Funktionsinitialisierung (ES5 +)

Manchmal können sich Funktionen weitgehend unbemerkt einschleichen; das ist der Fall mit Accessor-Funktionen. Hier ist ein Beispiel:

var obj = {
    value: 0,
    get f() {
        return this.value;
    },
    set f(v) {
        this.value = v;
    }
};
console.log(obj.f);         // 0
console.log(typeof obj.f);  // "number"

Beachten Sie, dass ich die Funktion nicht verwendet habe ()! Das ist, weil es ein ist Accessor-Funktion für eine Eigenschaft. Wir bekommen und setzen die Eigenschaft auf die übliche Weise, aber hinter den Kulissen wird die Funktion aufgerufen.

Sie können auch Accessor-Funktionen mit erstellen Object.defineProperty, Object.definePropertiesund das weniger bekannte zweite Argument zu Object.create.

Pfeilfunktionsausdruck (ES2015 +)

ES2015 bringt uns das Pfeilfunktion. Hier ist ein Beispiel:

var a = [1, 2, 3];
var b = a.map(n => n * 2);
console.log(b.join(", ")); // 2, 4, 6

Siehst du das n => n * 2 Ding versteckt in der map() Anruf? Das ist eine Funktion.

Ein paar Dinge über Pfeilfunktionen:

  1. Sie haben keine eigenen this. Stattdessen sie nah vorbei das this des Kontexts, in dem sie definiert sind. (Sie schließen auch vorbei arguments und gegebenenfalls super.) Dies bedeutet, dass die this in ihnen ist das gleiche wie die this wo sie erstellt werden und nicht geändert werden können.

  2. Wie Sie oben bemerkt haben, verwenden Sie das Schlüsselwort nicht function; stattdessen verwenden Sie =>.

Das n => n * 2 Beispiel oben ist eine Form von ihnen. Wenn Sie mehrere Argumente haben, um die Funktion zu übergeben, verwenden Sie parens:

var a = [1, 2, 3];
var b = a.map((n, i) => n * i);
console.log(b.join(", ")); // 0, 2, 6

(Erinnere dich daran Array#map übergibt den Eintrag als erstes Argument und den Index als zweites Argument.)

In beiden Fällen ist der Körper der Funktion nur ein Ausdruck; Der Rückgabewert der Funktion ist automatisch das Ergebnis dieses Ausdrucks (Sie verwenden keinen expliziten return).

Wenn Sie mehr als nur einen Ausdruck verwenden, verwenden Sie {} und ein explizites return (wenn Sie einen Wert zurückgeben müssen), wie üblich:

var a = [
  {first: "Joe", last: "Bloggs"},
  {first: "Albert", last: "Bloggs"},
  {first: "Mary", last: "Albright"}
];
a = a.sort((a, b) => {
  var rv = a.last.localeCompare(b.last);
  if (rv === 0) {
    rv = a.first.localeCompare(b.first);
  }
  return rv;
});
console.log(JSON.stringify(a));

Die Version ohne { ... } heißt eine Pfeilfunktion mit einem Ausdruckskörper oder prägnanter Körper. (Auch ein prägnant Pfeilfunktion.) Der mit { ... } Definieren des Körpers ist eine Pfeilfunktion mit a Funktionskörper. (Auch ein ausführlich Pfeilfunktion.)

Methodendeklaration im Objektinitialisierer (ES2015 +)

ES2015 ermöglicht eine kürzere Form der Deklaration einer Eigenschaft, die auf eine Funktion verweist; es sieht aus wie das:

var o = {
    foo() {
    }
};

das Äquivalent in ES5 und früher wäre:

var o = {
    foo: function foo() {
    }
};

Deklarationen von Konstruktoren und Methoden in class (ES2015 +)

ES2015 bringt uns class Syntax, einschließlich deklarierter Konstruktoren und Methoden:

class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    getFullName() {
        return this.firstName + " " + this.lastName;
    }
}

Es gibt oben zwei Funktionsdeklarationen: Eine für den Konstruktor, die den Namen erhält Personund eins für getFullName, dem eine Funktion zugewiesen ist Person.prototype.


544
2018-03-04 13:35



Sprechen wir über den globalen Kontext, sowohl die var Aussage und a FunctionDeclaration Am Ende wird ein erstellt nicht löschbar Eigenschaft für das globale Objekt, aber der Wert von beiden kann überschrieben werden.

Der feine Unterschied zwischen den beiden Möglichkeiten ist, dass, wenn die Variable Instanziierung Prozess führt (vor der eigentlichen Codeausführung) alle mit deklarierten Bezeichner aus var wird mit initialisiert undefinedund die von der FunctionDeclarationEs wird von diesem Moment an verfügbar sein, zum Beispiel:

 alert(typeof foo); // 'function', it's already available
 alert(typeof bar); // 'undefined'
 function foo () {}
 var bar = function () {};
 alert(typeof bar); // 'function'

Die Zuordnung der bar  FunctionExpression findet bis zur Laufzeit statt.

Eine globale Eigenschaft, die von a erstellt wurde FunctionDeclaration kann wie ein variabler Wert problemlos überschrieben werden, z. B .:

 function test () {}
 test = null;

Ein weiterer offensichtlicher Unterschied zwischen Ihren beiden Beispielen besteht darin, dass die erste Funktion keinen Namen hat, aber die zweite hat sie, was beim Debuggen wirklich nützlich sein kann (d. H. Einen Aufruf-Stack untersuchen).

Über dein bearbeitetes erstes Beispiel (foo = function() { alert('hello!'); };), es ist eine nicht deklarierte Aufgabe, ich würde Sie sehr ermutigen, immer die var Stichwort.

Mit einer Aufgabe, ohne die var Wenn der referenzierte Bezeichner nicht in der Scope-Kette gefunden wird, wird er zu a löschbar Eigenschaft des globalen Objekts.

Auch nicht angemeldete Aufgaben werfen ein ReferenceError auf ECMAScript 5 unter strikter Modus.

Muss gelesen werden:

Hinweis: Diese Antwort wurde von zusammengeführt eine andere Frage, in der der Hauptfokus und das Missverständnis des OP bestand, dass die mit a FunctionDeclaration, konnte nicht überschrieben werden, was nicht der Fall ist.


133
2017-08-08 19:32



Die beiden Code-Snippets, die Sie dort gepostet haben, verhalten sich für fast alle Zwecke genauso.

Der Unterschied im Verhalten ist jedoch der mit der ersten Variante (var functionOne = function() {}), kann diese Funktion nur nach diesem Punkt im Code aufgerufen werden.

Mit der zweiten Variante (function functionTwo()), steht die Funktion für Code zur Verfügung, der oberhalb der deklarierten Funktion läuft.

Dies liegt daran, dass bei der ersten Variante die Funktion der Variablen zugewiesen wird foo zur Laufzeit. In der zweiten wird die Funktion dieser Kennung zugewiesen, foozur Zeit der Analyse.

Weitere technische Informationen

JavaScript hat drei Möglichkeiten, Funktionen zu definieren.

  1. Dein erster Ausschnitt zeigt a Funktionsausdruck. Dies beinhaltet die Verwendung der "Funktion" -Operator um eine Funktion zu erstellen - das Ergebnis dieses Operators kann in jeder Variablen oder Objekteigenschaft gespeichert werden. Der Funktionsausdruck ist auf diese Weise mächtig. Der Funktionsausdruck wird oft als "anonyme Funktion" bezeichnet, da er keinen Namen haben muss.
  2. Ihr zweites Beispiel ist a Funktionsdeklaration. Dies verwendet die "Funktion" -Aussage um eine Funktion zu erstellen. Die Funktion wird zur Analysezeit zur Verfügung gestellt und kann überall in diesem Bereich aufgerufen werden. Sie können es später noch in einer Variablen oder Objekteigenschaft speichern.
  3. Die dritte Art, eine Funktion zu definieren, ist die "Function ()" -Konstruktor, die nicht in Ihrem ursprünglichen Post angezeigt wird. Es wird nicht empfohlen, dies zu verwenden, da es genauso funktioniert wie eval()Das hat seine Probleme.

111
2018-04-20 04:54



Eine bessere Erklärung für Gregs Antwort

functionTwo();
function functionTwo() {
}

Warum kein Fehler? Uns wurde immer beigebracht, dass Ausdrücke von oben nach unten ausgeführt werden (??)

Weil:

Funktionsdeklarationen und Variablendeklarationen werden immer verschoben (hoisted) unsichtbar an den Anfang ihres enthaltenden Bereichs durch den JavaScript-Interpreter. Funktionsparameter und sprachdefinierte Namen sind natürlich schon da. ben Kirsche

Dies bedeutet, dass Code wie folgt lautet:

functionOne();                  ---------------      var functionOne;
                                | is actually |      functionOne();
var functionOne = function(){   | interpreted |-->
};                              |    like     |      functionOne = function(){
                                ---------------      };

Beachten Sie, dass der Zuordnungsteil der Deklarationen nicht gehisst wurde. Nur der Name wird gehisst.

Aber im Fall von Funktionsdeklarationen wird auch der gesamte Funktionskörper hochgezogen:

functionTwo();              ---------------      function functionTwo() {
                            | is actually |      };
function functionTwo() {    | interpreted |-->
}                           |    like     |      functionTwo();
                            ---------------

91
2017-08-09 02:45



Andere Kommentatoren haben bereits den semantischen Unterschied der beiden obigen Varianten behandelt. Ich wollte einen stilistischen Unterschied feststellen: Nur die "Zuweisung" Variation kann eine Eigenschaft eines anderen Objekts setzen.

Ich baue oft JavaScript-Module mit einem Muster wie diesem:

(function(){
    var exports = {};

    function privateUtil() {
            ...
    }

    exports.publicUtil = function() {
            ...
    };

    return exports;
})();

Mit diesem Muster verwenden Ihre öffentlichen Funktionen alle Zuweisungen, während Ihre privaten Funktionen Deklarationen verwenden.

(Beachten Sie auch, dass die Zuweisung ein Semikolon nach der Anweisung erfordern sollte, während die Deklaration dies verbietet.)


83
2018-03-03 19:19