Warum kann ich eine Funktion verwenden, bevor sie in JavaScript definiert ist?

167

Dieser Code funktioniert auch in verschiedenen Browsern immer:

function fooCheck() {
  alert(internalFoo()); // We are using internalFoo() here...

  return internalFoo(); // And here, even though it has not been defined...

  function internalFoo() { return true; } //...until here!
}

fooCheck();

Ich konnte jedoch keinen einzigen Hinweis darauf finden, warum es funktionieren sollte. Ich habe dies zum ersten Mal in John Resigs Präsentationsnotiz gesehen, aber es wurde nur erwähnt. Es gibt dort oder irgendwo keine Erklärung dafür.

Könnte mich bitte jemand aufklären?

Edu Felipe
quelle
3
In neueren Versionen von Firefox funktioniert dies nicht, wenn sich der Code in einem Try / Catch befindet. Siehe diese Geige: jsfiddle.net/qzzc1evt
Joshua Smith

Antworten:

217

Die functionDeklaration ist magisch und bewirkt, dass ihre Kennung gebunden wird, bevor etwas in ihrem Codeblock * ausgeführt wird.

Dies unterscheidet sich von einer Zuordnung mit einem functionAusdruck, der in normaler Top-Down-Reihenfolge ausgewertet wird.

Wenn Sie das Beispiel geändert haben, um zu sagen:

var internalFoo = function() { return true; };

es würde aufhören zu arbeiten.

Die Funktionsdeklaration ist syntaktisch ziemlich unabhängig vom Funktionsausdruck, obwohl sie fast identisch aussieht und in einigen Fällen mehrdeutig sein kann.

Dies ist im ECMAScript-Standard , Abschnitt 10.1.3, dokumentiert . Leider ist ECMA-262 selbst nach Standards kein sehr lesbares Dokument!

*: die enthaltende Funktion, den Block, das Modul oder das Skript.

Bobince
quelle
Ich denke, es ist wirklich nicht lesbar. Ich habe gerade den Abschnitt gelesen, auf den Sie in 10.1.3 hingewiesen haben, und nicht verstanden, warum die dortigen Bestimmungen dieses Verhalten verursachen würden. Danke für die Auskunft.
Edu Felipe
2
@bobince Okay, ich begann an mir selbst zu zweifeln, als ich auf dieser Seite keine einzige Erwähnung des Begriffs „Heben“ fand. Hoffentlich haben diese Kommentare genug Google Juice ™, um die Dinge in Ordnung zu bringen :)
Mathias Bynens
2
Dies ist eine beliebte Frage / Antwort-Kombination. Erwägen Sie eine Aktualisierung mit einem Link / Auszug zur mit Anmerkungen versehenen ES5-Spezifikation. (Welches ist ein bisschen zugänglicher.)
1
Dieser Artikel hat einige Beispiele: JavaScript-Scoping-and-Hoisting
Lombas
Ich habe festgestellt, dass einige Bibliotheken die Funktion vor der Definition verwenden, sogar einige Sprachen erlauben dies offiziell, z. Haskell. Um ehrlich zu sein, ist dies möglicherweise keine schlechte Sache, da Sie in einigen Fällen etwas ausdrucksstärker schreiben können.
Windmaomao
27

Es heißt HOISTING - Aufrufen (Aufrufen) einer Funktion, bevor sie definiert wurde.

Zwei verschiedene Arten von Funktionen, über die ich schreiben möchte, sind:

Ausdrucksfunktionen und Deklarationsfunktionen

  1. Ausdrucksfunktionen:

    Funktionsausdrücke können in einer Variablen gespeichert werden, sodass sie keine Funktionsnamen benötigen. Sie werden auch als anonyme Funktion (eine Funktion ohne Namen) benannt.

    Um diese Funktionen aufzurufen (aufzurufen), benötigen sie immer einen Variablennamen . Diese Art von Funktion funktioniert nicht, wenn sie aufgerufen wird, bevor sie definiert wurde, was bedeutet, dass hier kein Heben stattfindet. Wir müssen immer zuerst die Ausdrucksfunktion definieren und sie dann aufrufen.

    let lastName = function (family) {
     console.log("My last name is " + family);
    };
    let x = lastName("Lopez");

    So können Sie es in ECMAScript 6 schreiben:

    lastName = (family) => console.log("My last name is " + family);
    
    x = lastName("Lopez");
  2. Deklarationsfunktionen:

    Mit der folgenden Syntax deklarierte Funktionen werden nicht sofort ausgeführt. Sie werden "zur späteren Verwendung gespeichert" und später ausgeführt, wenn sie aufgerufen (aufgerufen) werden. Diese Art von Funktion funktioniert, wenn Sie sie VOR oder NACH der Definition aufrufen. Wenn Sie eine Deklarationsfunktion aufrufen, bevor sie definiert wurde, funktioniert das Heben ordnungsgemäß.

    function Name(name) {
      console.log("My cat's name is " + name);
    }
    Name("Chloe");

    Hebebeispiel:

    Name("Chloe");
    function Name(name) {
       console.log("My cat's name is " + name);
    }
Negin
quelle
let fun = theFunction; fun(); function theFunction() {}wird auch funktionieren (Knoten und Browser)
fider
14

Der Browser liest Ihren HTML-Code von Anfang bis Ende und kann ihn ausführen, während er gelesen und in ausführbare Blöcke (Variablendeklarationen, Funktionsdefinitionen usw.) analysiert wird. Er kann jedoch zu jedem Zeitpunkt nur das verwenden, was vor diesem Punkt im Skript definiert wurde.

Dies unterscheidet sich von anderen Programmierkontexten, die Ihren gesamten Quellcode verarbeiten (kompilieren), ihn möglicherweise mit Bibliotheken verknüpfen, die Sie zum Auflösen von Referenzen benötigen, und ein ausführbares Modul erstellen, an dem die Ausführung beginnt.

Ihr Code kann auf benannte Objekte (Variablen, andere Funktionen usw.) verweisen, die weiter unten definiert sind. Sie können jedoch keinen Referenzcode ausführen, bis alle Teile verfügbar sind.

Wenn Sie sich mit JavaScript vertraut machen, werden Sie sich genau bewusst, dass Sie die Dinge in der richtigen Reihenfolge schreiben müssen.

Revision: Um die akzeptierte Antwort (oben) zu bestätigen, verwenden Sie Firebug, um den Skriptabschnitt einer Webseite zu durchlaufen. Sie werden sehen, dass es von Funktion zu Funktion springt und nur die erste Zeile besucht, bevor es tatsächlich Code ausführt.

dkretz
quelle
3

Einige Sprachen verlangen, dass Bezeichner vor der Verwendung definiert werden. Ein Grund dafür ist, dass der Compiler einen einzigen Durchgang für den Quellcode verwendet.

Wenn es jedoch mehrere Durchgänge gibt (oder einige Schecks verschoben werden), können Sie ohne diese Anforderung perfekt leben. In diesem Fall wird der Code wahrscheinlich zuerst gelesen (und interpretiert) und dann werden die Links gesetzt.

Toon Krijthe
quelle
2

Ich habe nur wenig JavaScript verwendet. Ich bin mir nicht sicher, ob dies helfen wird, aber es sieht dem, worüber Sie sprechen, sehr ähnlich und kann einen Einblick geben:

http://www.dustindiaz.com/javascript-function-declaration-ambiguity/

RailsSon
quelle
Der Link ist nicht mehr tot.
mwclarke
1
Der Link ist tot.
Jerome Indefenzo
Hier ist ein Schnappschuss von archive.org. Es sieht so aus, als hätte der Autor seine gesamte Website wegen veralteter Inhalte heruntergefahren , nicht dass dieser Blog-Beitrag in diese Kategorie fällt.
Jkmartindale
1

Der Hauptteil der Funktion "internalFoo" muss zur Analysezeit irgendwohin gehen. Wenn also der Code vom JS-Interpreter gelesen (auch als Analyse bezeichnet) wird, wird die Datenstruktur für die Funktion erstellt und der Name zugewiesen.

Erst später, wenn der Code ausgeführt wird, versucht JavaScript tatsächlich herauszufinden, ob "internalFoo" existiert und was es ist und ob es aufgerufen werden kann usw.

Aaron Digulla
quelle
-4

Aus dem gleichen Grund wird fooim globalen Namespace immer Folgendes eingefügt:

if (test condition) {
    var foo;
}
Andrew Hedges
quelle
8
Eigentlich ist es aus ganz anderen Gründen. Der ifBlock erstellt keinen Bereich, während ein function()Block immer einen erstellt. Der wahre Grund war, dass die Definition globaler Javascript-Namen in der Kompilierungsphase erfolgt, sodass der Name auch dann definiert wird, wenn der Code nicht ausgeführt wird. (Entschuldigung, der Kommentar hat so lange gedauert)
Edu Felipe