Sorpreso che la variabile globale abbia un valore indefinito in JavaScript


87

Oggi sono rimasto completamente sorpreso quando ho visto che una variabile globale ha undefined valore in alcuni casi.

Esempio:

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

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

Fornisce output come

undefined
20

Ecco perché il motore JavaScript considera il valore globale come undefined . So che JavaScript è un linguaggio interpretato. Come può considerare le variabili nella funzione?

È una trappola del motore JavaScript?

Risposte:


175

Questo fenomeno è noto come: JavaScript Variable Hoisting .

In nessun momento stai accedendo alla variabile globale nella tua funzione; stai accedendo solo alla valuevariabile locale .

Il tuo codice è equivalente al seguente:

var value = 10;

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

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

test();

Sei ancora sorpreso undefined?


Spiegazione:

Questo è qualcosa in cui ogni programmatore JavaScript prima o poi si imbatte. In poche parole, qualunque variabile dichiari viene sempre sollevata in cima alla tua chiusura locale. Quindi, anche se hai dichiarato la tua variabile dopo la prima console.logchiamata, è ancora considerata come se l'avessi dichiarata prima.
Tuttavia, viene sollevata solo la parte della dichiarazione; il compito, invece, non lo è.

Quindi, quando hai chiamato per la prima volta console.log(value), stavi facendo riferimento alla tua variabile dichiarata localmente, a cui non è stato ancora assegnato nulla; quindi undefined.

Ecco un altro esempio :

var test = 'start';

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

end();
alert(test);

Cosa pensi che questo avvertirà? No, non limitarti a leggere, pensaci. Qual è il valore di test?

Se hai detto qualcosa di diverso da start, ti sei sbagliato. Il codice sopra è equivalente a questo:

var test = 'start';

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

end();
alert(test);

in modo che la variabile globale non sia mai influenzata.

Come puoi vedere, indipendentemente da dove metti la tua dichiarazione di variabile, viene sempre issata in cima alla chiusura locale.


Nota a margine:

Questo vale anche per le funzioni.

Considera questo pezzo di codice :

test("Won't work!");

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

che ti darà un errore di riferimento:

Errore di riferimento non rilevato: il test non è definito

Questo allontana molti sviluppatori, poiché questo pezzo di codice funziona bene:

test("Works!");

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

Il motivo, come affermato, è perché la parte di assegnazione non viene sollevata. Quindi nel primo esempio, quando è test("Won't work!")stata eseguita, la testvariabile è già stata dichiarata, ma non le è stata ancora assegnata la funzione.

Nel secondo esempio, non stiamo usando l'assegnazione di variabili. Piuttosto, stiamo usando sintassi corretta dichiarazione di funzione, che fa ottenere la funzione completamente issato.


Ben Cherry ha scritto un eccellente articolo su questo argomento , appropriatamente intitolato JavaScript Scoping and Hoisting .
Leggilo. Ti darà l'intera immagine in dettaglio.


27
Questa è una buona spiegazione. Tuttavia, mi manca una soluzione in cui è possibile accedere alle variabili globali all'interno di una funzione.
DuKes0mE

1
@ DuKes0mE - puoi sempre accedere alle variabili globali all'interno di una funzione.
Joseph Silber

3
sì, e perché il caso A dal post di apertura non funziona e dice che è indefinito? Capisco che verrà interpretato come una var locale invece che globale, ma come sarà allora globale?
DuKes0mE

4
per accedere alla variabile globale utilizzare window.value
Venkat Reddy

1
quando è stato implementato questo stile di sollevamento variabile? è sempre stato uno standard in javascript?
Dieskim

54

Sono rimasto un po 'deluso dal fatto che il problema qui sia stato spiegato, ma nessuno ha proposto una soluzione. Se vuoi accedere a una variabile globale nell'ambito della funzione senza che la funzione crei prima una var locale non definita, fai riferimento alla var comewindow.varName


8
Sì, fa schifo che gli altri ragazzi non abbiano proposto una soluzione perché questo è il primo risultato nei risultati di Google. Hanno quasi risposto alla domanda effettiva posta sul fatto che si tratta di una trappola nel motore js .... sigh -> grazie per la tua risposta
user1567453

2
Questa è la differenza tra la conoscenza teorica e il fare le cose. Grazie per quella risposta!
hansTheFranz

Per me, è un errore aver modificato un nome globale ovunque in qualsiasi funzione. Almeno crea confusione. Rende necessaria una ricerca su Google, nel peggiore dei casi. Grazie
dcromley

Javascript continua a sorprendermi ogni giorno che passa. Grazie amico, la risposta è stata utile.
Raf

Grazie per la soluzione, l'ho trovata dopo che una var globale era indefinita ma solo in Safari. Sono stati visualizzati altri file "include" e variabili globali come "google", quindi ho copiato l'approccio utilizzato da Google: window.globalVarFromJS = window.globalVarFromJS || {}; Poi ho trovato la tua soluzione, quindi ho pensato di aggiungerla.
Ralph Hinkley

10

Le variabili in JavaScript hanno sempre un ambito a livello di funzione. Anche se sono stati definiti nel mezzo della funzione, sono visibili prima. Fenomeni simili possono essere osservati con il sollevamento funzionale.

Detto questo, il primo console.log(value)vede la valuevariabile (quella interna che ombreggia quella esterna value), ma non è stata ancora inizializzata. Puoi pensarlo come se tutte le dichiarazioni di variabili fossero state spostate implicitamente all'inizio della funzione ( non il blocco di codice più interno), mentre le definizioni fossero lasciate nello stesso posto.

Guarda anche


Amo sempre le parole semplici +1 :)
Jashwant

3

C'è una variabile globale value, ma quando il controllo entra nella testfunzione, valueviene dichiarata un'altra variabile, che ombreggia quella globale. Poiché le dichiarazioni di variabili ( ma non le assegnazioni ) in JavaScript vengono sollevate all'inizio dell'ambito in cui sono dichiarate:

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

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

Nota che lo stesso vale per le dichiarazioni di funzione, il che significa che puoi chiamare una funzione prima che sembri essere definita nel tuo codice:

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

Vale anche la pena notare che quando si combinano i due in un'espressione di funzione, la variabile sarà undefinedfino a quando non avrà luogo l'assegnazione, quindi non è possibile chiamare la funzione fino a quando ciò non accade:

var test = function() {
    //Do stuff
};
test(); //Have to call the function after the assignment

0
  1. Il modo più semplice per mantenere l'accesso alle variabili esterne (non solo di ambito globale) è, ovviamente, cercare di non dichiararle nuovamente con lo stesso nome nelle funzioni; semplicemente non usare var lì. Si consiglia l'uso di regole di denominazione descrittive appropriate . Con quelli, sarà difficile finire con variabili denominate come valore (questo aspetto non è necessariamente correlato all'esempio nella domanda poiché questo nome di variabile potrebbe essere stato dato per semplicità).

  2. Se la funzione può essere riutilizzata altrove e quindi non vi è alcuna garanzia che la variabile esterna sia effettivamente definita in quel nuovo contesto, è possibile utilizzare la funzione Eval . È lento in questa operazione, quindi non è consigliato per le funzioni che richiedono prestazioni elevate:

    if (typeof variable === "undefined")
    {
        eval("var variable = 'Some value';");
    }
    
  3. Se la variabile dell'ambito esterno a cui si desidera accedere è definita in una funzione denominata, potrebbe essere collegata alla funzione stessa in primo luogo e quindi accedervi da qualsiasi punto del codice, sia da funzioni profondamente nidificate o gestori di eventi esterni a tutto il resto. Si noti che l'accesso alle proprietà è molto più lento e richiederebbe di modificare il modo in cui si programma, quindi non è consigliato a meno che non sia realmente necessario: Variabili come proprietà delle funzioni (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");
    

0

Stavo riscontrando lo stesso problema anche con le variabili globali. Il mio problema, ho scoperto, era che la variabile globale non persistesse tra i file html.

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

Ho provato a fare riferimento a myVar e myVarTwo nel file HTML caricato, ma ho ricevuto l'errore non definito. Storia lunga / giorno breve, ho scoperto che potevo fare riferimento alle variabili usando:

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

        var myHTMLVar = this.parent.myVar

        /* other stuff here */
    </script>
</html>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.