2013-03-13 7 views
6

Capisco i problemi con scope globale e le variabili javascript e la loro indesiderabilità generale; e che li trovi ovunque. La seguente (in un browser) è equivalente:Javascript Global Scope Assignment

var foo = 3; // foo === 3, window.foo === 3 
bazz = 10; // bazz === 10, window.bazz === 10 

Dichiarazione di una variabile con la parola chiave var nell'ambito globale è lo stesso dichiarandolo senza var in qualsiasi parte del codice: la variabile è assegnato alla radice (finestra) oggetto.

Una tecnica Vedo un sacco (ad esempio, la creazione di Google Analytics) è questo:

var _gaq = _gaq || []; 

... e seguo il ragionamento che se _gaq è stato dichiarato l'uso che, se non crea come un array. Permette alla codifica trascurata di non sovrascrivere i valori già assegnati alla variabile globale _gaq.

Quello che non capisco è perché questo genera un errore:

_gaq = _gaq || []; 

Guardano equivalente a me: _gaq dovrebbe prendere il valore di _gaq o essere inizializzato come un array. Ma lancia un errore di riferimento - la mia domanda è: perché sono diversi?

+1

Non sono esattamente equivalenti, differiscono nel comportamento quando si tenta di ['delete'] (http://perfectionkills.com/understanding-delete/). – Bergi

+0

La migliore spiegazione di "incarichi non dichiarati" e perché sono diversi dagli incarichi globali ('foo = 0'! =' Var foo = 0' nell'ambito globale) Posso trovare è ** [su questo blog] (http : //perfectionkills.com/understanding-delete/) ** che mi ha aiutato a capire la differenza intrinseca. –

risposta

6

Non è mai possibile leggere le variabili che non sono state dichiarate ed è ciò che si sta tentando con l'espressione _gaq || [] nell'ultimo caso.

In questo caso

_gaq = _gaq || []; 

_qaq non era stata dichiarata prima e quando si valuta il lato destro (_gaq || []), si genera l'errore.

Ecco spiegazione passo passo di ciò che avviene in questo caso:

L'operatore di assegnazione è descritta nel section 11.13.1 della specifica:

The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:

1. Let lref be the result of evaluating LeftHandSideExpression .
2. Let rref be the result of evaluating AssignmentExpression .
...

LeftHandSideExpression è _gaq, il AssignmentExpression è _gqa || [].

Quindi viene valutato il primo _qaq, che risulta in un unresolvable reference, poiché la variabile _gaq non è dichiarata. Questa valutazione non produce un errore.

Quindi viene valutato _gqa || []. Questo è un LogicalORExpression ed è descritto in section 11.11 come LogicalORExpression || LogicalANDExpression.In questo caso, LogicalORExpression, il lato sinistro, è _gaq e LogicalANDExpression, sul lato destro, è [].
L'espressione è valutata come segue:

1. Let lref be the result of evaluating LogicalORExpression .
2. Let lval be GetValue(lref) .
...

sappiamo già che lref sarà un riferimento irrisolvibile perché _gaq non è stato dichiarato. Quindi, consente di avere uno sguardo che cosa GetValue sta facendo (definito in section 8.7.1, V è il valore passato a GetValue):

1. If Type(V) is not Reference , return V .
2. Let base be the result of calling GetBase(V) .
3. If IsUnresolvableReference(V) , throw a ReferenceError exception.
...

Come si può vedere, un errore di ReferenceError è gettato nella terza fase di questa procedura, che a sua turn viene eseguito valutando il lato destro del compito, e qui è dove viene generato l'errore.

Quindi, perché non succede con var _gaq = _gaq || [];?

Questa linea:

var _gaq = _gaq || []; 

è in realtà

var _gaq; 
_gaq = _gaq || []; 

a causa di qualcosa che si chiama hoisting [MDN]. Ciò significa che quando viene valutato _gaq, sarà non risultato in un riferimento irrisolvibile, ma un riferimento con valore undefined.

(Se la variabile _gaq è già dichiarato (e, potenzialmente, ha un valore), quindi var _gaq non avrà alcun effetto.)


Se si desidera creare _gaq a livello globale dall'interno di una funzione, fare si esplicitamente facendo riferimento a window:

window._gaq = window._gaq || []; 
+0

Sembra giusto! "Non puoi mai leggere variabili che non sono state dichiarate", non sono d'accordo con questa affermazione. 'window.x' per esempio legge bene e restituisce il tipo di valore primitivo' undefined' se la variabile 'x' non è stata dichiarata in' window' (assumendo che non abbiamo definito 'window.x') –

+0

@Benjamin: Ma in questo caso, 'window.x' è una proprietà dell'oggetto' window', non una variabile. E anche se le variabili globali sono proprietà dell'oggetto globale, non è che si acceda all'oggetto 'window', quando si fa riferimento a una variabile globale" implicitamente ", le proprietà di' window' vengono semplicemente inserite nella catena dell'ambito delle funzioni. –

+0

Grazie che lo rende molto più chiaro. Tuttavia, suggerisce la domanda: che cosa "magia" sta accadendo quando si scrive semplicemente "foo = 10"? Se il motore javascript deduce o crea un "var foo" implicito; affermazione, allora perché non lo fa per il mio esempio? –

5

Se lo _gaq a destra di = non è stato dichiarato precedente con var, verrà generato un errore di riferimento. Stai provando a fare riferimento a una variabile che non esiste. La "magia" funziona solo per assegnando a variabili non esistenti.

È proprio come dire x = y + 1; il problema non è l'inesistente x, ma l'inesistente .

+0

Grazie. Quindi capisco che il motore cerca di calcolare il valore della variabile e gli errori perché non è stato dichiarato. Ciò ha senso. Dove ho sbagliato è che lasciare la "var" per i globals non è un modo stenografico/opzionale per dichiararli, anche se è il modo in cui appare per lo più. –

+1

@Part: No, omettendo 'var' è ok come abbreviazione. Il problema non è scrivere una variabile non dichiarata, ne sta leggendo una. Proprio come nell'esempio in questa risposta 'x = y + 1'. 'x' diventa globale se' y' è stato dichiarato ma poiché non lo è, genera un errore che 'y' non è dichiarato. Il lato destro viene valutato per primo, quindi nel caso di 'x = x || 42; 'stai provando a leggere' x' prima che venga creato. –

+0

Vedere sopra, ma penso che sia davvero importante capire che il sollevamento si verifica solo quando si scrive var _even nello scope_ globale, e quindi 'var x = x || 0' funziona, ma' x = x || 0' doesn ' t. Potresti dire "beh ovviamente" ma a meno che tu non stia "cortocircuitando" in questo modo, sembra che una varietà globale sia semplicemente cruft. Da qui la mia iniziale confusione. Grazie per le risposte. –

1

Questo genera un errore perché la variabile non si trova nel contesto catena del exec corrente contesto di ution. L'accesso a una variabile che non può essere risolto comporterà un errore.

_gaq = _gaq || []; 

Questo, d'altra parte, cercherà di risolvere _gac cercando di guardare per come membro dell'oggetto window, wich si rivela essere l'oggetto globale contesto 'titolare'. La differenza in questo caso è che non genererà un errore, ma window._gaq restituirà un valore non definito, poiché la proprietà non viene trovata nell'oggetto della finestra.

_gaq = window._gaq || []; 

Quindi, dal momento che l'oggetto contesto globale è la finestra (quando si parla di browser), se _gaq è definito, questo due dichiarazioni avranno lo stesso effetto. La differenza verrà notata quando _gaq non è definito e l'accesso ad esso utilizzando l'oggetto window può avere il vantaggio di non ricevere un errore.

1

Il concetto di base qui è hoisting ed è spesso difficile nella pratica. Le variabili sono definite all'inizio dell'ambito di una funzione mentre l'assegnazione si verifica ancora dove è definita.

Con questa var _gaq = _gaq variabile sia in effetti definito prima viene eseguita la riga effettiva di codice per l'assegnazione. Ciò significa che quando si verifica l'assegnazione, la variabile è già presente nell'ambito della finestra. Senza la var di fronte a _gaq non si verifica il sollevamento e quindi _gaq non esiste ancora quando l'assegnazione viene eseguita causando l'errore di riferimento.

Se volete vedere in azione è possibile verificare quando la variabile _gaq viene aggiunto all'oggetto finestra con il seguente:

function printIsPropOnWindow(propToCheck) 
{ 
    for (prop in window) 
    { 
     if (prop == propToCheck) 
     { 
      console.warn('YES, prop ' + prop + ' was on the window object'); 
      return; 
     } 
    } 
    console.warn('NO, prop ' + propToCheck + ' was NOT on the window object'); 
} 


try { 
    var _gaq = function() { 
     printIsPropOnWindow("_gaq"); 
     return a; 
    }(); 
} catch (ex) { 
    printIsPropOnWindow("_gaq"); 
} 
_gaq = "1"; 
printIsPropOnWindow("_gaq"); 

Se si tenta questa volta come è e una volta con la var prima _gaq rimosso si vedranno risultati molto diversi perché uno ha sollevato il _gaq e l'altro no.

+0

Grazie. Penso che il mio fraintendimento fondamentale sia che scrivere 'foo = true' è esattamente lo stesso di scrivere' var foo = true', e che nel primo caso la 'var' era implicita. Per essere onesti, penso che questo sia un malinteso abbastanza comune, perché per la maggior parte degli scopi sono gli stessi. Tuttavia, come sottolinea Bergi, il primo diventa proprietà della finestra ... e anche il secondo. Tranne che non lo è davvero: l'eliminazione fallirà. In qualche modo sottile sono diversi. –

+1

@Party: La differenza * tra 'var foo = ...' e 'foo = ...' in ambito globale non ha nulla a che fare con il tuo problema. Solo il fatto che le dichiarazioni variabili sono state issate. –

+0

@Party Ark: Felix Kling ha ragione. Il sollevamento cambia * quando * foo viene aggiunto all'oggetto finestra. E il tuo problema è che stai cercando di assegnare quella variabile prima che venga aggiunta all'ambito della finestra in modo che la linea fallisca con l'errore. Se hai la var viene issato-> il che significa che viene aggiunto all'oggetto window proprio all'inizio di qualsiasi ambito tu sia. – purgatory101