Dies ist weder ein Umfangsproblem noch ein Schließungsproblem. Das Problem liegt im Verständnis zwischen Deklarationen und Ausdrücken .
JavaScript-Code wird in zwei Phasen verarbeitet, da selbst die erste Version von JavaScript von Netscape und die erste Kopie von Microsoft davon verarbeitet werden:
Phase 1: Kompilierung - In dieser Phase wird der Code in einen Syntaxbaum kompiliert (und je nach Engine Bytecode oder Binärcode).
Phase 2: Ausführung - Der analysierte Code wird dann interpretiert.
Die Syntax für die Funktionsdeklaration ist:
function name (arguments) {code}
Argumente sind natürlich optional (Code ist ebenfalls optional, aber wozu dient das?).
Mit JavaScript können Sie jedoch auch Funktionen mithilfe von Ausdrücken erstellen . Die Syntax für Funktionsausdrücke ähnelt Funktionsdeklarationen, außer dass sie im Ausdruckskontext geschrieben sind. Und Ausdrücke sind:
- Alles rechts von einem
=
Zeichen (oder :
auf Objektliteralen).
- Alles in Klammern
()
.
- Parameter zu Funktionen (dies wird tatsächlich bereits von 2 abgedeckt).
Ausdrücke im Gegensatz zu Deklarationen werden eher in der Ausführungsphase als in der Kompilierungsphase verarbeitet. Und deshalb ist die Reihenfolge der Ausdrücke wichtig.
Um dies zu verdeutlichen:
(function() {
setTimeout(someFunction, 10);
var someFunction = function() { alert('here1'); };
})();
Phase 1: Zusammenstellung. Der Compiler erkennt, dass die Variable someFunction
definiert ist, und erstellt sie. Standardmäßig haben alle erstellten Variablen den Wert undefined. Beachten Sie, dass der Compiler zu diesem Zeitpunkt noch keine Werte zuweisen kann, da der Interpreter möglicherweise einen Code ausführen muss, um einen zuzuweisenden Wert zurückzugeben. Und zu diesem Zeitpunkt führen wir noch keinen Code aus.
Phase 2: Ausführung. Der Interpreter sieht, dass Sie die Variable someFunction
an setTimeout übergeben möchten. Und so ist es auch. Leider ist der aktuelle Wert von someFunction
undefiniert.
(function() {
setTimeout(someFunction, 10);
function someFunction() { alert('here2'); }
})();
Phase 1: Zusammenstellung. Der Compiler sieht, dass Sie eine Funktion mit dem Namen someFunction deklarieren, und erstellt sie daher.
Phase 2: Der Interpreter sieht, dass Sie someFunction
an setTimeout übergeben möchten. Und so ist es auch. Der aktuelle Wert von someFunction
ist die kompilierte Funktionsdeklaration.
(function() {
setTimeout(function() { someFunction(); }, 10);
var someFunction = function() { alert('here3'); };
})();
Phase 1: Zusammenstellung. Der Compiler sieht, dass Sie eine Variable deklariert haben, someFunction
und erstellt sie. Nach wie vor ist sein Wert undefiniert.
Phase 2: Ausführung. Der Interpreter übergibt eine anonyme Funktion an setTimeout, um sie später auszuführen. In dieser Funktion wird angezeigt, dass Sie die Variable verwenden, someFunction
sodass die Variable geschlossen wird. Zu diesem Zeitpunkt ist der Wert von someFunction
noch undefiniert. Dann sehen Sie, wie Sie eine Funktion zuweisen someFunction
. Zu diesem Zeitpunkt ist der Wert von someFunction
nicht mehr undefiniert. Eine Hundertstelsekunde später wird das setTimeout ausgelöst und die someFunction aufgerufen. Da sein Wert nicht mehr undefiniert ist, funktioniert es.
Fall 4 ist wirklich eine andere Version von Fall 2, in die ein bisschen Fall 3 eingeworfen ist. An dem Punkt, someFunction
an dem setTimeout übergeben wird, existiert es bereits, weil es deklariert wurde.
Zusätzliche Klarstellung:
Sie fragen sich vielleicht, warum setTimeout(someFunction, 10)
kein Abschluss zwischen der lokalen Kopie von someFunction und der an setTimeout übergebenen Kopie erstellt wird. Die Antwort darauf ist, dass Funktionsargumente in JavaScript immer, immer als Wert übergeben werden, wenn es sich um Zahlen oder Zeichenfolgen handelt, oder als Referenz für alles andere. SetTimeout erhält also nicht die Variable someFunction, die an ihn übergeben wird (was bedeuten würde, dass ein Abschluss erstellt wird), sondern nur das Objekt, auf das someFunction verweist (was in diesem Fall eine Funktion ist). Dies ist der in JavaScript am häufigsten verwendete Mechanismus zum Aufheben von Verschlüssen (z. B. in Schleifen).