Überrascht, dass die globale Variable in JavaScript einen undefinierten Wert hat

87

Heute war ich völlig überrascht, als ich sah, dass eine globale Variable undefinedin bestimmten Fällen einen Wert hat .

Beispiel:

var value = 10;
function test() {
    //A
    console.log(value);
    var value = 20;

    //B
    console.log(value);
}
test();

Gibt die Ausgabe als aus

undefined
20

Warum berücksichtigt die JavaScript-Engine hier den globalen Wert als undefined. Ich weiß, dass JavaScript eine interpretierte Sprache ist. Wie kann es Variablen in der Funktion berücksichtigen?

Ist das eine Falle der JavaScript-Engine?

iamjustcoder
quelle

Antworten:

175

Dieses Phänomen ist bekannt als: JavaScript Variable Hoisting .

Sie greifen zu keinem Zeitpunkt auf die globale Variable in Ihrer Funktion zu. Sie greifen immer nur auf die lokale valueVariable zu.

Ihr Code entspricht dem Folgenden:

var value = 10;

function test() {
    var value;
    console.log(value);

    value = 20;
    console.log(value);
}

test();

Immer noch überrascht, dass Sie bekommen undefined?


Erläuterung:

Dies ist etwas, auf das jeder JavaScript-Programmierer früher oder später stößt. Einfach ausgedrückt, was auch immer Variablen deklarieren Sie sind immer hochgezogen an die Spitze Ihrer lokalen Schließung. Obwohl Sie Ihre Variable nach dem ersten console.logAufruf deklariert haben , wird immer noch davon ausgegangen, dass Sie sie zuvor deklariert haben.
Es wird jedoch nur der Deklarationsteil gehisst; die Zuordnung ist dagegen nicht.

Als Sie das erste Mal aufgerufen haben console.log(value), haben Sie auf Ihre lokal deklarierte Variable verwiesen, der noch nichts zugewiesen wurde. daher undefined.

Hier ist ein weiteres Beispiel :

var test = 'start';

function end() {
    test = 'end';
    var test = 'local';
}

end();
alert(test);

Was glaubst du, wird dies alarmieren? Nein, lesen Sie nicht einfach weiter, denken Sie darüber nach. Was ist der Wert von test?

Wenn Sie etwas anderes sagten als start, haben Sie sich geirrt. Der obige Code entspricht diesem:

var test = 'start';

function end() {
    var test;
    test = 'end';
    test = 'local';
}

end();
alert(test);

so dass die globale Variable nie betroffen ist.

Wie Sie sehen, wird Ihre Variablendeklaration unabhängig davon, wo Sie sie ablegen, immer an die Spitze Ihres lokalen Verschlusses gehisst .


Randnotiz:

Dies gilt auch für Funktionen.

Betrachten Sie diesen Code :

test("Won't work!");

test = function(text) { alert(text); }

Das gibt Ihnen einen Referenzfehler:

Nicht erfasster ReferenceError: Test ist nicht definiert

Dies wirft viele Entwickler ab, da dieser Code gut funktioniert:

test("Works!");

function test(text) { alert(text); }

Der Grund dafür ist, wie angegeben, dass der Zuordnungsteil nicht angehoben wird. Im ersten Beispiel, als test("Won't work!")es ausgeführt wurde, wurde die testVariable bereits deklariert, ihr muss jedoch noch die Funktion zugewiesen werden.

Im zweiten Beispiel verwenden wir keine Variablenzuweisung. Vielmehr verwenden wir die richtige Funktionsdeklaration Syntax, die tut die Funktion bekommen vollständig gehisst.


Ben Cherry hat dazu einen ausgezeichneten Artikel mit dem passenden Titel JavaScript Scoping and Hoisting geschrieben .
Lies es. Sie erhalten das gesamte Bild im Detail.

Joseph Silber
quelle
27
Dies ist eine gute Erklärung. Ich vermisse jedoch eine Lösung, bei der Sie innerhalb einer Funktion auf globale Variablen zugreifen können.
DuKes0mE
1
@ DuKes0mE - Sie können innerhalb einer Funktion immer auf globale Variablen zugreifen.
Joseph Silber
3
Ja, und warum funktioniert Fall A aus dem Eröffnungsbeitrag nicht und sagt, dass er undefiniert ist? Ich verstehe, dass es als lokale Variable anstatt als globale interpretiert wird, aber wie wird es dann global sein?
DuKes0mE
4
Um auf die globale Variable zuzugreifen, verwenden Sie window.value
Venkat Reddy
1
Wann wurde diese Art des variablen Hebens umgesetzt? War dies in Javascript schon immer Standard?
Dieskim
53

Ich war etwas enttäuscht, dass das Problem hier erklärt wird, aber niemand schlug eine Lösung vor. Wenn Sie auf eine globale Variable im Funktionsumfang zugreifen möchten, ohne dass die Funktion zuerst eine undefinierte lokale Variable erstellt, verweisen Sie auf die Variable alswindow.varName

Amalgovinus
quelle
8
Ja, es ist schade, dass die anderen Jungs keine Lösung vorgeschlagen haben, weil dies das erste Ergebnis in den Google-Ergebnissen ist. Sie haben fast die eigentliche Frage beantwortet, die gestellt wurde, dass es sich um eine Falle in der js-Engine handelt .... seufz -> danke für deine Antwort
user1567453
2
Das ist der Unterschied zwischen theoretischem Wissen und dem Erledigen von Aufgaben. Danke für diese Antwort!
HansTheFranz
Für mich ist es ein Fehler, dass ich irgendwo in einer Funktion einen globalen Namen geändert habe. Zumindest für Verwirrung. Im schlimmsten Fall ist eine Google-Suche erforderlich. Danke
dcromley
Javascript überrascht mich jeden Tag, der vergeht. Danke Mann, die Antwort war hilfreich.
Raf
Vielen Dank für die Lösung. Ich habe dies gefunden, nachdem eine globale Variable undefiniert war, aber nur in Safari. Andere 'include'-Dateien und globale Variablen wie' google 'wurden angezeigt, daher habe ich den von Google verwendeten Ansatz kopiert: window.globalVarFromJS = window.globalVarFromJS || {}; Dann habe ich Ihre Lösung gefunden und dachte, ich würde sie ergänzen.
Ralph Hinkley
10

Variablen in JavaScript haben immer einen funktionsweiten Bereich. Auch wenn sie in der Mitte der Funktion definiert wurden, sind sie vorher sichtbar. Ähnliche Phänomene können beim Funktionsheben beobachtet werden.

Davon abgesehen console.log(value)sieht die erste die valueVariable (die innere, die die äußere beschattet value), aber sie wurde noch nicht initialisiert. Sie können sich vorstellen, dass alle Variablendeklarationen implizit an den Anfang der Funktion verschoben wurden ( nicht innerste Codeblock), während die Definitionen an derselben Stelle verbleiben.

Siehe auch

Tomasz Nurkiewicz
quelle
Ich liebe immer einfache Wörter +1 :)
Jashwant
3

Es gibt eine globale Variable value, aber wenn die Steuerung in die testFunktion eintritt , wird eine andere valueVariable deklariert, die die globale schattiert. Da Variablendeklarationen ( aber keine Zuweisungen ) in JavaScript an den Anfang des Bereichs gehoben werden, in dem sie deklariert werden:

//value == undefined (global)
var value = 10;
//value == 10 (global)

function test() {
    //value == undefined (local)
    var value = 20;
    //value == 20 (local)
}
//value == 10 (global)

Beachten Sie, dass dies auch für Funktionsdeklarationen gilt. Dies bedeutet, dass Sie eine Funktion aufrufen können, bevor sie in Ihrem Code definiert zu sein scheint:

test(); //Call the function before it appears in the source
function test() {
    //Do stuff
}

Es ist auch erwähnenswert, dass, wenn Sie die beiden zu einem Funktionsausdruck kombinieren, die Variable so lange ist, undefinedbis die Zuweisung erfolgt, sodass Sie die Funktion erst dann aufrufen können, wenn dies geschieht:

var test = function() {
    //Do stuff
};
test(); //Have to call the function after the assignment
James Allardice
quelle
0
  1. Der einfachste Weg, den Zugriff auf äußere Variablen (nicht nur von globalem Umfang) beizubehalten, besteht natürlich darin, zu versuchen, sie in Funktionen nicht unter demselben Namen erneut zu deklarieren. benutze dort einfach nicht var . Die Verwendung geeigneter beschreibender Namensregeln wird empfohlen. Bei diesen wird es schwierig sein, Variablen mit dem Namen value zu erhalten (dieser Aspekt hängt nicht unbedingt mit dem Beispiel in der Frage zusammen, da dieser Variablenname möglicherweise der Einfachheit halber angegeben wurde).

  2. Wenn die Funktion an anderer Stelle wiederverwendet werden kann und daher nicht garantiert werden kann, dass die äußere Variable tatsächlich in diesem neuen Kontext definiert ist, kann die Eval- Funktion verwendet werden. Dieser Vorgang ist langsam und wird daher nicht für leistungsintensive Funktionen empfohlen:

    if (typeof variable === "undefined")
    {
        eval("var variable = 'Some value';");
    }
  3. Wenn die äußere Bereichsvariable, auf die Sie zugreifen möchten, in einer benannten Funktion definiert ist, wird sie möglicherweise zuerst an die Funktion selbst angehängt und dann von einer beliebigen Stelle im Code aus aufgerufen - sei es von tief verschachtelten Funktionen oder Ereignishandlern außerhalb von alles andere. Beachten Sie, dass der Zugriff auf Eigenschaften viel langsamer ist und Sie die Art und Weise Ihrer Programmierung ändern müssen. Daher wird dies nicht empfohlen, es sei denn, dies ist wirklich erforderlich: Variablen als Eigenschaften von Funktionen (JSFiddle) :

    // (the wrapper-binder is only necessary for using variables-properties
    // via "this"instead of the function's name)
    var functionAsImplicitObjectBody = function()
    {
        function someNestedFunction()
        {
            var redefinableVariable = "redefinableVariable's value from someNestedFunction";
            console.log('--> functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
            console.log('--> redefinableVariable: ', redefinableVariable);
        }
        var redefinableVariable = "redefinableVariable's value from someFunctionBody";
        console.log('this.variableAsProperty: ', this.variableAsProperty);
        console.log('functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
        console.log('redefinableVariable: ', redefinableVariable);
        someNestedFunction();
    },
    functionAsImplicitObject = functionAsImplicitObjectBody.bind(functionAsImplicitObjectBody);
    functionAsImplicitObjectBody.variableAsProperty = "variableAsProperty's value, set at time stamp: " + (new Date()).getTime();
    functionAsImplicitObject();
    
    // (spread-like operator "..." provides passing of any number of arguments to
    // the target internal "func" function in as many steps as necessary)
    var functionAsExplicitObject = function(...arguments)
    {
        var functionAsExplicitObjectBody = {
            variableAsProperty: "variableAsProperty's value",
            func: function(argument1, argument2)
            {
                function someNestedFunction()
                {
                    console.log('--> functionAsExplicitObjectBody.variableAsProperty: ',
                        functionAsExplicitObjectBody.variableAsProperty);
                }
                console.log("argument1: ", argument1);
                console.log("argument2: ", argument2);
                console.log("this.variableAsProperty: ", this.variableAsProperty);
                someNestedFunction();
            }    
        };
        return functionAsExplicitObjectBody.func(...arguments);
    };
    functionAsExplicitObject("argument1's value", "argument2's value");
DDRRSS
quelle
0

Ich hatte sogar mit globalen Variablen das gleiche Problem. Ich stellte fest, dass mein Problem darin bestand, dass globale Variablen zwischen HTML-Dateien nicht bestehen bleiben.

<script>
    window.myVar = 'foo';
    window.myVarTwo = 'bar';
</script>
<object type="text/html" data="/myDataSource.html"></object>

Ich habe versucht, myVar und myVarTwo in der geladenen HTML-Datei zu referenzieren, aber den undefinierten Fehler erhalten. Lange Rede / kurzer Tag, ich entdeckte, dass ich die Variablen mit folgenden Referenzen referenzieren konnte:

<!DOCTYPE html>
<html lang="en">
    <!! other stuff here !!>
    <script>

        var myHTMLVar = this.parent.myVar

        /* other stuff here */
    </script>
</html>
Nathan Sutherland
quelle