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).
Der Umfang von Javascript basiert auf Funktionen und nicht ausschließlich auf lexikalischem Geltungsbereich. das bedeutet, dass
Somefunction1 wird vom Beginn der umschließenden Funktion definiert, aber sein Inhalt ist bis zur Zuweisung undefiniert.
Im zweiten Beispiel ist die Zuweisung Teil der Deklaration, sodass sie nach oben verschoben wird.
Im dritten Beispiel ist die Variable vorhanden, wenn der anonyme innere Verschluss definiert ist. Sie wird jedoch erst 10 Sekunden später verwendet. Bis dahin wurde der Wert zugewiesen.
Das vierte Beispiel hat sowohl den zweiten als auch den dritten Grund zu arbeiten
quelle
Weil
someFunction1
zum Zeitpunkt dersetTimeout()
Ausführung des Anrufs noch nicht zugewiesen wurde .someFunction3 kann wie ein ähnlicher Fall aussehen, aber da Sie vorbei eine Funktion Verpackung
someFunction3()
zusetTimeout()
dem Anruf in diesem Fall zusomeFunction3()
erst später ausgewertet.quelle
someFunction2
aber noch nicht zugewiesen, wenn der Aufruf vonsetTimeout()
ausgeführt wird ...?function
Schlüsselwort entspricht nicht genau dem Zuweisen einer anonymen Funktion zu einer Variablen. Als deklarierte Funktionenfunction foo()
werden an den Anfang des aktuellen Bereichs "gehisst", während Variablenzuweisungen an dem Punkt erfolgen, an dem sie geschrieben werden.Dies klingt wie ein grundlegender Fall, wenn Sie ein gutes Verfahren befolgen, um Ärger zu vermeiden. Deklarieren Sie Variablen und Funktionen, bevor Sie sie verwenden, und deklarieren Sie Funktionen wie folgt:
function name (arguments) {code}
Vermeiden Sie es, sie mit var zu deklarieren. Dies ist nur schlampig und führt zu Problemen. Wenn Sie es sich zur Gewohnheit machen, alles zu deklarieren, bevor Sie es verwenden, werden die meisten Ihrer Probleme in großer Eile verschwinden. Wenn ich Variablen deklariere, würde ich sie sofort mit einem gültigen Wert initialisieren, um sicherzustellen, dass keine von ihnen undefiniert ist. Ich neige auch dazu, Code einzuschließen, der nach gültigen Werten globaler Variablen sucht, bevor eine Funktion sie verwendet. Dies ist ein zusätzlicher Schutz vor Fehlern.
Die technischen Details, wie das alles funktioniert, ähneln der Physik, wie eine Handgranate funktioniert, wenn Sie damit spielen. Mein einfacher Rat ist, überhaupt nicht mit Handgranaten zu spielen.
Einige einfache Deklarationen am Anfang des Codes lösen möglicherweise die meisten dieser Probleme, aber eine Bereinigung des Codes ist möglicherweise noch erforderlich.
Zusätzlicher Hinweis:
Ich habe einige Experimente durchgeführt und es scheint, dass es keine Rolle spielt, in welcher Reihenfolge sie alle Ihre Funktionen auf die hier beschriebene Weise deklarieren. Wenn Funktion A Funktion B verwendet, muss Funktion B dies nicht tun vor Funktion A deklariert werden.
Deklarieren Sie also zuerst alle Ihre Funktionen, dann Ihre globalen Variablen und setzen Sie dann Ihren anderen Code zuletzt ein. Befolgen Sie diese Faustregeln und Sie können nichts falsch machen. Es ist möglicherweise sogar am besten, Ihre Erklärungen in den Kopf der Webseite und Ihren anderen Code in den Text einzufügen, um die Durchsetzung dieser Regeln sicherzustellen.
quelle