Qual è l'ambito delle variabili in JavaScript? Hanno lo stesso scopo all'interno anziché all'esterno di una funzione? O importa? Inoltre, dove sono memorizzate le variabili se sono definite a livello globale?
Qual è l'ambito delle variabili in JavaScript? Hanno lo stesso scopo all'interno anziché all'esterno di una funzione? O importa? Inoltre, dove sono memorizzate le variabili se sono definite a livello globale?
Risposte:
JavaScript ha scopi e chiusure lessicali (chiamati anche statici). Ciò significa che puoi capire l'ambito di un identificatore guardando il codice sorgente.
I quattro ambiti sono:
Al di fuori dei casi speciali di ambito globale e di modulo, le variabili vengono dichiarate utilizzando var
(ambito funzione), let
(ambito blocco) e const
(ambito blocco). La maggior parte delle altre forme di dichiarazione dell'identificatore ha l'ambito del blocco in modalità rigorosa.
L'ambito è l'area della base di codice su cui è valido un identificatore.
Un ambiente lessicale è una mappatura tra i nomi degli identificatori e i valori ad essi associati.
L'ambito è formato da un annidamento collegato di ambienti lessicali, con ogni livello nell'annidamento corrispondente a un ambiente lessicale di un contesto di esecuzione degli antenati.
Questi ambienti lessicali collegati formano una "catena" di ambito. La risoluzione dell'identificatore è il processo di ricerca lungo questa catena per un identificatore corrispondente.
La risoluzione dell'identificatore si verifica solo in una direzione: verso l'esterno. In questo modo, gli ambienti lessicali esterni non possono "vedere" negli ambienti lessicali interni.
Esistono tre fattori pertinenti nel decidere l' ambito di un identificatore in JavaScript:
Alcuni dei modi in cui gli identificatori possono essere dichiarati:
var
, let
econst
var
in modalità non rigorosa)import
dichiarazionieval
Alcuni identificatori di località possono essere dichiarati:
Gli identificatori dichiarati utilizzando var
hanno un ambito di funzione , a parte quando vengono dichiarati direttamente nel contesto globale, nel qual caso vengono aggiunti come proprietà sull'oggetto globale e hanno un ambito globale. Esistono regole separate per il loro utilizzo nelle eval
funzioni.
Gli identificatori dichiarati utilizzando let
e const
hanno un ambito di blocco , a parte quando vengono dichiarati direttamente nel contesto globale, nel qual caso hanno un ambito globale.
Nota: let
, const
e var
sono tutti issato . Ciò significa che la loro posizione logica di definizione è la parte superiore del loro ambito di applicazione (blocco o funzione). Tuttavia, le variabili dichiarate in uso let
e const
non possono essere lette o assegnate fino a quando il controllo non ha superato il punto di dichiarazione nel codice sorgente. Il periodo intermedio è noto come zona morta temporale.
function f() {
function g() {
console.log(x)
}
let x = 1
g()
}
f() // 1 because x is hoisted even though declared with `let`!
I nomi dei parametri delle funzioni sono assegnati al corpo della funzione. Si noti che c'è una leggera complessità in questo. Le funzioni dichiarate come argomenti predefiniti si chiudono sull'elenco dei parametri e non sul corpo della funzione.
Le dichiarazioni di funzione hanno ambito di blocco in modalità rigorosa e ambito di funzione in modalità non rigorosa. Nota: la modalità non rigorosa è un insieme complicato di regole emergenti basate sulle stravaganti implementazioni storiche di diversi browser.
Le espressioni di funzioni denominate sono mirate a se stesse (ad es. Ai fini della ricorsione).
In modalità non rigorosa, le proprietà implicitamente definite sull'oggetto globale hanno ambito globale, poiché l'oggetto globale si trova nella parte superiore della catena dell'ambito. In modalità rigorosa non sono consentiti.
Nelle eval
stringhe, le variabili dichiarate utilizzando var
verranno inserite nell'ambito corrente o, se eval
utilizzate indirettamente, come proprietà dell'oggetto globale.
Quanto segue gettare un ReferenceError perché i nomi x
, y
e z
non hanno alcun significato al di fuori della funzione f
.
function f() {
var x = 1
let y = 1
const z = 1
}
console.log(typeof x) // undefined (because var has function scope!)
console.log(typeof y) // undefined (because the body of the function is a block)
console.log(typeof z) // undefined (because the body of the function is a block)
Quanto segue genererà un ReferenceError per y
e z
, ma non per x
, perché la visibilità di x
non è limitata dal blocco. I blocchi che definiscono i corpi di strutture di controllo come if
, for
e while
, allo stesso modo si comportano.
{
var x = 1
let y = 1
const z = 1
}
console.log(x) // 1
console.log(typeof y) // undefined because `y` has block scope
console.log(typeof z) // undefined because `z` has block scope
Di seguito, x
è visibile al di fuori del ciclo perché var
ha ambito di funzione:
for(var x = 0; x < 5; ++x) {}
console.log(x) // 5 (note this is outside the loop!)
... a causa di questo comportamento devi stare attento a chiudere le variabili dichiarate usando var
nei cicli. C'è solo un'istanza di variabile x
dichiarata qui, e si trova logicamente fuori dal ciclo.
Le seguenti stampe 5
, cinque volte, quindi stampa 5
per la sesta volta per l' console.log
esterno del ciclo:
for(var x = 0; x < 5; ++x) {
setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop
}
console.log(x) // note: visible outside the loop
Di seguito viene stampato undefined
perché x
è a blocchi. I callback vengono eseguiti uno alla volta in modo asincrono. Un nuovo comportamento per le let
variabili significa che ogni funzione anonima si è chiusa su una diversa variabile denominata x
(diversamente da come avrebbe fatto con var
), e così vengono stampati gli interi 0
attraverso 4
.:
for(let x = 0; x < 5; ++x) {
setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables
}
console.log(typeof x) // undefined
Quanto segue NON genererà a ReferenceError
perché la visibilità di x
non è limitata dal blocco; verrà comunque stampato undefined
perché la variabile non è stata inizializzata (a causa if
dell'istruzione).
if(false) {
var x = 1
}
console.log(x) // here, `x` has been declared, but not initialised
Una variabile dichiarata nella parte superiore di un for
ciclo che utilizza let
è associata al corpo del ciclo:
for(let x = 0; x < 10; ++x) {}
console.log(typeof x) // undefined, because `x` is block-scoped
Quanto segue lancerà a ReferenceError
perché la visibilità di x
è limitata dal blocco:
if(false) {
let x = 1
}
console.log(typeof x) // undefined, because `x` is block-scoped
Le variabili dichiarate utilizzando var
, let
o const
sono tutti scope ai moduli:
// module1.js
var x = 0
export function f() {}
//module2.js
import f from 'module1.js'
console.log(x) // throws ReferenceError
Quanto segue dichiarerà una proprietà sull'oggetto globale, poiché le variabili dichiarate usando var
nel contesto globale, vengono aggiunte come proprietà all'oggetto globale:
var x = 1
console.log(window.hasOwnProperty('x')) // true
let
e const
nel contesto globale non aggiungere proprietà all'oggetto globale, ma hanno comunque portata globale:
let x = 1
console.log(window.hasOwnProperty('x')) // false
I parametri della funzione possono essere considerati dichiarati nel corpo della funzione:
function f(x) {}
console.log(typeof x) // undefined, because `x` is scoped to the function
I parametri del blocco di cattura sono definiti nel corpo del blocco di cattura:
try {} catch(e) {}
console.log(typeof e) // undefined, because `e` is scoped to the catch block
Le espressioni di funzioni con nome hanno un ambito solo per l'espressione stessa:
(function foo() { console.log(foo) })()
console.log(typeof foo) // undefined, because `foo` is scoped to its own expression
In modalità non rigorosa, le proprietà implicitamente definite sull'oggetto globale sono di ambito globale. In modalità rigorosa viene visualizzato un errore.
x = 1 // implicitly defined property on the global object (no "var"!)
console.log(x) // 1
console.log(window.hasOwnProperty('x')) // true
In modalità non rigorosa, le dichiarazioni di funzione hanno ambito di funzione. In modalità rigorosa hanno ambito di blocco.
'use strict'
{
function foo() {}
}
console.log(typeof foo) // undefined, because `foo` is block-scoped
L'ambito è definito come la regione lessicale del codice su cui è valido un identificatore.
In JavaScript, ogni oggetto-funzione ha un [[Environment]]
riferimento nascosto che è un riferimento all'ambiente lessicale del contesto di esecuzione (stack frame) all'interno del quale è stato creato.
Quando si richiama una funzione, [[Call]]
viene chiamato il metodo nascosto . Questo metodo crea un nuovo contesto di esecuzione e stabilisce un collegamento tra il nuovo contesto di esecuzione e l'ambiente lessicale dell'oggetto funzione. Lo fa copiando il [[Environment]]
valore sull'oggetto funzione, in un riferimento esterno campo di nell'ambiente lessicale del nuovo contesto di esecuzione.
Si noti che questo collegamento tra il nuovo contesto di esecuzione e l'ambiente lessicale dell'oggetto funzione è chiamato chiusura .
Pertanto, in JavaScript, l'ambito viene implementato tramite ambienti lessicali collegati tra loro in una "catena" da riferimenti esterni. Questa catena di ambienti lessicali è chiamata catena dell'ambito e la risoluzione dell'identificatore si verifica cercando nella catena un identificatore corrispondente.
Per saperne di più .
Javascript utilizza le catene dell'ambito per stabilire l'ambito di una determinata funzione. In genere esiste un ambito globale e ogni funzione definita ha il proprio ambito nidificato. Qualsiasi funzione definita all'interno di un'altra funzione ha un ambito locale collegato alla funzione esterna. È sempre la posizione nella fonte che definisce l'ambito.
Un elemento nella catena dell'ambito è fondamentalmente una mappa con un puntatore al suo ambito padre.
Quando si risolve una variabile, javascript inizia nell'ambito più interno e cerca verso l'esterno.
Le variabili dichiarate a livello globale hanno un ambito globale. Le variabili dichiarate all'interno di una funzione hanno come ambito quella funzione e ombreggiano variabili globali con lo stesso nome.
(Sono sicuro che ci sono molte sottigliezze che i veri programmatori JavaScript saranno in grado di indicare in altre risposte. In particolare mi sono imbattuto in questa pagina su cosa this
significa esattamente in qualsiasi momento. Spero che questo link più introduttivo sia sufficiente per iniziare però .)
Tradizionalmente, JavaScript ha davvero solo due tipi di ambito:
Non approfondirò questo aspetto, poiché ci sono già molte altre risposte che spiegano la differenza.
Le specifiche JavaScript più recenti ora consentono anche un terzo ambito:
Tradizionalmente, crei le tue variabili in questo modo:
var myVariable = "Some text";
Le variabili dell'ambito del blocco vengono create in questo modo:
let myVariable = "Some text";
Per comprendere la differenza tra ambito funzionale e ambito del blocco, considerare il codice seguente:
// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here
function loop(arr) {
// i IS known here, but undefined
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
for( var i = 0; i < arr.length; i++ ) {
// i IS known here, and has a value
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
};
// i IS known here, and has a value
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
for( let j = 0; j < arr.length; j++ ) {
// i IS known here, and has a value
// j IS known here, and has a value
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
};
// i IS known here, and has a value
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
}
loop([1,2,3,4]);
for( var k = 0; k < arr.length; k++ ) {
// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here
};
for( let l = 0; l < arr.length; l++ ) {
// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS known here, and has a value
};
loop([1,2,3,4]);
// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here
Qui, possiamo vedere che la nostra variabile j
è nota solo nel primo ciclo, ma non prima e dopo. Tuttavia, la nostra variabile i
è nota nell'intera funzione.
Inoltre, considerare che le variabili con ambito di blocco non sono note prima di essere dichiarate perché non vengono issate. Inoltre, non è possibile dichiarare nuovamente la stessa variabile con ambito blocco all'interno dello stesso blocco. Ciò rende le variabili con ambito di blocco meno soggette a errori rispetto alle variabili con ambito globale o funzionale, che vengono sollevate e che non generano errori in caso di dichiarazioni multiple.
L'utilizzo sicuro o meno oggi dipende dal tuo ambiente:
Se stai scrivendo codice JavaScript sul lato server ( Node.js ), puoi tranquillamente utilizzare l' let
istruzione.
Se stai scrivendo un codice JavaScript sul lato client e usi un transpiler basato su browser (come Traceur o babel-standalone ), puoi tranquillamente utilizzare l' let
istruzione, tuttavia è probabile che il tuo codice sia tutt'altro che ottimale rispetto alle prestazioni.
Se stai scrivendo codice JavaScript sul lato client e usi un transpiler basato su Nodo (come lo script della shell traceur o Babel ), puoi tranquillamente usare l' let
istruzione. E poiché il tuo browser conoscerà solo il codice traspilato, gli svantaggi delle prestazioni dovrebbero essere limitati.
Se stai scrivendo codice JavaScript lato client e non usi un transpiler, devi considerare il supporto del browser.
Questi sono alcuni browser che non supportano let
affatto:
Per una panoramica aggiornata di quali browser supportano la let
dichiarazione al momento della lettura di questa risposta, consultare questa Can I Use
pagina .
(*) Le variabili con portata globale e funzionale possono essere inizializzate e utilizzate prima di essere dichiarate perché le variabili JavaScript vengono issate . Ciò significa che le dichiarazioni sono sempre in cima all'ambito.
Ecco un esempio:
<script>
var globalVariable = 7; //==window.globalVariable
function aGlobal( param ) { //==window.aGlobal();
//param is only accessible in this function
var scopedToFunction = {
//can't be accessed outside of this function
nested : 3 //accessible by: scopedToFunction.nested
};
anotherGlobal = {
//global because there's no `var`
};
}
</script>
Ti consigliamo di indagare sulle chiusure e su come utilizzarle per creare membri privati .
La chiave, a quanto ho capito, è che Javascript ha l'ambito scoping a livello di funzione rispetto all'ambito del blocco C più comune.
In "Javascript 1.7" (l'estensione di Mozilla a Javascript) si possono anche dichiarare variabili con ambito di blocco con l' let
istruzione :
var a = 4;
let (a = 3) {
alert(a); // 3
}
alert(a); // 4
let
.
L'idea di scoping in JavaScript quando originariamente progettato da Brendan Eich è nata dal linguaggio di scripting HyperCard HyperTalk .
In questa lingua, i display erano fatti in modo simile a una pila di schede. C'era una carta principale indicata come sfondo. Era trasparente e può essere visto come la carta in fondo. Qualsiasi contenuto su questa scheda base è stato condiviso con le carte posizionate sopra di essa. Ogni carta posta in cima aveva il suo contenuto che aveva la precedenza sulla carta precedente, ma se lo desiderava aveva comunque accesso alle carte precedenti.
Questo è esattamente il modo in cui è progettato il sistema di scoping JavaScript. Ha solo nomi diversi. Le carte in JavaScript sono note come Execution Contexts ECMA . Ognuno di questi contesti contiene tre parti principali. Un ambiente variabile, un ambiente lessicale e questo legame. Tornando al riferimento delle carte, l'ambiente lessicale contiene tutto il contenuto delle carte precedenti più in basso nella pila. Il contesto corrente è in cima allo stack e qualsiasi contenuto dichiarato verrà archiviato nell'ambiente variabile. L'ambiente variabile avrà la precedenza in caso di collisioni di denominazione.
Questo legame punterà all'oggetto contenitore. A volte gli ambiti o i contesti di esecuzione cambiano senza che l'oggetto contenitore cambi, come ad esempio in una funzione dichiarata in cui l'oggetto contenitore può essere window
o una funzione di costruzione.
Questi contesti di esecuzione vengono creati ogni volta che viene trasferito il controllo. Il controllo viene trasferito quando inizia l'esecuzione del codice e ciò viene principalmente eseguito dall'esecuzione della funzione.
Questa è la spiegazione tecnica. In pratica, è importante ricordarlo in JavaScript
Applicando questo a uno degli esempi precedenti (5. "Chiusura") in questa pagina, è possibile seguire la pila di contesti di esecuzione. In questo esempio ci sono tre contesti nello stack. Sono definiti dal contesto esterno, dal contesto nella funzione immediatamente invocata chiamato da var six e dal contesto nella funzione restituita all'interno della funzione immediatamente invocata da var six.
i ) Il contesto esterno. Ha un ambiente variabile di a = 1
ii ) Il contesto IIFE, ha un ambiente lessicale di a = 1, ma un ambiente variabile di a = 6 che ha la precedenza nello stack
iii ) Il contesto della funzione restituita, ha un lessico ambiente di a = 6 e questo è il valore a cui fa riferimento l'avviso quando viene chiamato.
1) Esiste un ambito globale, un ambito di funzione e gli ambiti with e catch. Non esiste un ambito di livello "blocco" in generale per le variabili: le istruzioni with e catch aggiungono nomi ai loro blocchi.
2) Gli ambiti sono nidificati dalle funzioni fino all'ambito globale.
3) Le proprietà vengono risolte passando attraverso la catena del prototipo. L'istruzione with porta i nomi delle proprietà dell'oggetto nell'ambito lessicale definito dal blocco with.
EDIT: ECMAAScript 6 (Harmony) è stato progettato per supportare let, e so che chrome consente una bandiera 'armonia', quindi forse lo supporta ..
Let sarebbe un supporto per l'ambito scoping a livello di blocco, ma è necessario utilizzare la parola chiave per realizzarlo.
EDIT: Basandomi sul fatto che Benjamin ha sottolineato le dichiarazioni with e catch nei commenti, ho modificato il post e ne ho aggiunto altri. Sia le istruzioni with che catch introducono variabili nei rispettivi blocchi, e questo è un ambito di blocco. Queste variabili sono aliasate alle proprietà degli oggetti passati in esse.
//chrome (v8)
var a = { 'test1':'test1val' }
test1 // error not defined
with (a) { var test1 = 'replaced' }
test1 // undefined
a // a.test1 = 'replaced'
EDIT: esempio di chiarimento:
test1 è limitato al blocco with, ma è alias a a.test1. 'Var test1' crea una nuova variabile test1 nel contesto lessicale superiore (funzione o globale), a meno che non sia una proprietà di un - quale è.
Yikes! Fai attenzione usando 'with' - proprio come var è un noop se la variabile è già definita nella funzione, è anche un noop rispetto ai nomi importati dall'oggetto! Un piccolo avvertimento sul nome già definito renderebbe questo molto più sicuro. Personalmente non userò mai con questo per questo.
with
affermazione è una forma di scoping a blocchi, ma le catch
clausole sono una forma molto più comune (Curiosità, la v8 implementa catch
con a with
) - questa è praticamente l'unica forma di scoping a blocchi nello stesso JavaScript (vale a dire, funzione, globale, try / catch , con e loro derivati), tuttavia gli ambienti host hanno nozioni diverse di scoping, ad esempio eventi incorporati nel browser e il modulo vm di NodeJS.
Ho scoperto che molte persone che non conoscono JavaScript hanno difficoltà a comprendere che l'ereditarietà è disponibile per impostazione predefinita nella lingua e che finora l'ambito delle funzioni è l'unico ambito. Ho fornito un'estensione a un estetista che ho scritto alla fine dello scorso anno chiamato JSPretty. I colori funzione funzionano nell'ambito del codice e associano sempre un colore a tutte le variabili dichiarate in tale ambito. La chiusura viene dimostrata visivamente quando una variabile con un colore di un ambito viene utilizzata in un ambito diverso.
Prova la funzione su:
Guarda una demo su:
Visualizza il codice su:
Attualmente la funzione offre supporto per una profondità di 16 funzioni nidificate, ma attualmente non colora le variabili globali.
JavaScript ha solo due tipi di ambito:
var
parola chiave ha un ambito funzionale.Ogni volta che viene chiamata una funzione, viene creato un oggetto con ambito variabile (e incluso nella catena dell'ambito) seguito da variabili in JavaScript.
a = "global";
function outer(){
b = "local";
console.log(a+b); //"globallocal"
}
outer();
Scope chain ->
a
e la outer
funzione è al massimo livello nella catena dell'ambito.variable scope object
(e incluso nella catena dell'ambito) aggiunto con una variabile b
al suo interno.Ora, quando una variabile lo a
richiede, cerca prima l'ambito variabile più vicino e se la variabile non è presente, allora si sposta sull'oggetto successivo della catena dell'ambito variabile, che in questo caso è a livello di finestra.
Solo per aggiungere alle altre risposte, l'ambito è un elenco di ricerca di tutti gli identificatori dichiarati (variabili) e impone un rigido set di regole su come questi sono accessibili al codice attualmente in esecuzione. Questa ricerca può essere ai fini dell'assegnazione alla variabile, che è un riferimento LHS (lato sinistro), oppure può essere allo scopo di recuperare il suo valore, che è un riferimento RHS (lato destro). Queste ricerche sono ciò che il motore JavaScript sta facendo internamente durante la compilazione e l'esecuzione del codice.
Quindi, da questo punto di vista, penso che una foto sarebbe di aiuto che ho trovato nell'ebook Scopes and Closures di Kyle Simpson:
Citando dal suo ebook:
L'edificio rappresenta il set di regole dell'ambito nidificato del nostro programma. Il primo piano dell'edificio rappresenta l'ambito attualmente in esecuzione, ovunque tu sia. Il livello più alto dell'edificio è l'ambito globale. Risolvi i riferimenti LHS e RHS osservando il tuo piano attuale e, se non lo trovi, prendi l'ascensore per il piano successivo, guarda lì, poi il successivo e così via. Una volta arrivato all'ultimo piano (l'ambito globale), o trovi quello che stai cercando o no. Ma devi fermarti a prescindere.
Una cosa degna di nota è che "la ricerca dell'ambito si interrompe una volta trovata la prima corrispondenza".
Questa idea di "livelli di ambito" spiega perché "questo" può essere modificato con un ambito appena creato, se viene cercato in una funzione nidificata. Ecco un link che contiene tutti questi dettagli, tutto quello che volevi sapere sull'ambito javascript
esegui il codice. spero che questo possa dare un'idea di scoping
Name = 'global data';
document.Name = 'current document data';
(function(window,document){
var Name = 'local data';
var myObj = {
Name: 'object data',
f: function(){
alert(this.Name);
}
};
myObj.newFun = function(){
alert(this.Name);
}
function testFun(){
alert("Window Scope : " + window.Name +
"\nLocal Scope : " + Name +
"\nObject Scope : " + this.Name +
"\nCurrent document Scope : " + document.Name
);
}
testFun.call(myObj);
})(window,document);
Le variabili globali sono esattamente come le stelle globali (Jackie Chan, Nelson Mandela). Puoi accedervi (ottenere o impostare il valore), da qualsiasi parte della tua applicazione. Le funzioni globali sono come eventi globali (Capodanno, Natale). È possibile eseguirli (chiamare) da qualsiasi parte dell'applicazione.
//global variable
var a = 2;
//global function
function b(){
console.log(a); //access global variable
}
Se sei negli Stati Uniti, potresti conoscere Kim Kardashian, famigerata celebrità (in qualche modo riesce a fare i tabloid). Ma le persone al di fuori degli Stati Uniti non la riconosceranno. È una stella locale, legata al suo territorio.
Le variabili locali sono come stelle locali. È possibile accedervi (ottenere o impostare il valore) solo all'interno dell'ambito. Una funzione locale è come gli eventi locali: è possibile eseguire solo (celebrare) all'interno di tale ambito. Se si desidera accedervi dall'esterno dell'ambito, verrà visualizzato un errore di riferimento
function b(){
var d = 21; //local variable
console.log(d);
function dog(){ console.log(a); }
dog(); //execute local function
}
console.log(d); //ReferenceError: dddddd is not defined
Consulta questo articolo per una comprensione approfondita dell'ambito
Esistono QUASI solo due tipi di ambiti JavaScript:
Pertanto, qualsiasi blocco diverso dalle funzioni non crea un nuovo ambito. Ciò spiega perché i for-loop sovrascrivono le variabili con ambito esterno:
var i = 10, v = 10;
for (var i = 0; i < 5; i++) { var v = 5; }
console.log(i, v);
// output 5 5
Utilizzando invece le funzioni:
var i = 10, v = 10;
$.each([0, 1, 2, 3, 4], function(i) { var v = 5; });
console.log(i,v);
// output 10 10
Nel primo esempio, non vi era alcun ambito di blocco, quindi le variabili inizialmente dichiarate venivano sovrascritte. Nel secondo esempio, c'era un nuovo ambito dovuto alla funzione, quindi le variabili inizialmente dichiarate erano SHADOWED e non sovrascritte.
Questo è quasi tutto ciò che devi sapere in termini di scoping JavaScript, tranne:
Quindi puoi vedere che l'ambito JavaScript è in realtà estremamente semplice, anche se non sempre intuitivo. Alcune cose da tenere presente:
Quindi questo codice:
var i = 1;
function abc() {
i = 2;
var i = 3;
}
console.log(i); // outputs 1
è equivalente a:
var i = 1;
function abc() {
var i; // var declaration moved to the top of the scope
i = 2;
i = 3; // the assignment stays where it is
}
console.log(i);
Questo può sembrare contro intuitivo, ma ha senso dal punto di vista di un designer linguistico imperativo.
const
' e ' let
'Dovresti usare l'ambito del blocco per ogni variabile che crei, proprio come la maggior parte delle altre lingue principali. var
è obsoleto . Questo rende il tuo codice più sicuro e più gestibile.
const
dovrebbe essere usato per il 95% dei casi . Lo rende quindi il riferimento alla variabile non può cambiare. Le proprietà del nodo Matrice, Oggetto e DOM possono cambiare e dovrebbero probabilmente esserlo const
.
let
dovrebbe essere usato per qualsiasi variabile che si aspetta di essere riassegnata. Questo include all'interno di un ciclo for. Se si cambia valore oltre l'inizializzazione, utilizzare let
.
L'ambito del blocco indica che la variabile sarà disponibile solo tra parentesi quadre in cui è dichiarata. Ciò si estende agli ambiti interni, comprese le funzioni anonime create nell'ambito.
Prova questo curioso esempio. Nell'esempio seguente se a fosse un inizializzatore numerico a 0, vedresti 0 e poi 1. Tranne a è un oggetto e javascript passerà a f1 un puntatore di un anziché una copia di esso. Il risultato è che ricevi lo stesso avviso entrambe le volte.
var a = new Date();
function f1(b)
{
b.setDate(b.getDate()+1);
alert(b.getDate());
}
f1(a);
alert(a.getDate());
Esistono solo ambiti di funzione in JS. Non bloccare gli ambiti! Puoi vedere anche cosa sta sollevando.
var global_variable = "global_variable";
var hoisting_variable = "global_hoist";
// Global variables printed
console.log("global_scope: - global_variable: " + global_variable);
console.log("global_scope: - hoisting_variable: " + hoisting_variable);
if (true) {
// The variable block will be global, on true condition.
var block = "block";
}
console.log("global_scope: - block: " + block);
function local_function() {
var local_variable = "local_variable";
console.log("local_scope: - local_variable: " + local_variable);
console.log("local_scope: - global_variable: " + global_variable);
console.log("local_scope: - block: " + block);
// The hoisting_variable is undefined at the moment.
console.log("local_scope: - hoisting_variable: " + hoisting_variable);
var hoisting_variable = "local_hoist";
// The hoisting_variable is now set as a local one.
console.log("local_scope: - hoisting_variable: " + hoisting_variable);
}
local_function();
// No variable in a separate function is visible into the global scope.
console.log("global_scope: - local_variable: " + local_variable);
La mia comprensione è che ci sono 3 ambiti: ambito globale, disponibile a livello globale; ambito locale, disponibile per un'intera funzione indipendentemente dai blocchi; e l'ambito del blocco, disponibile solo per il blocco, l'istruzione o l'espressione su cui è stato utilizzato. L'ambito globale e locale sono indicati con la parola chiave "var", all'interno di una funzione o all'esterno, mentre l'ambito del blocco è indicato con la parola chiave "let".
Per coloro che credono che ci sia solo un ambito globale e locale, spiega perché Mozilla avrebbe un'intera pagina che descrive le sfumature dell'ambito del blocco in JS.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Un problema molto comune non ancora descritto che spesso si verificano nei coder front-end è l'ambito visibile a un gestore di eventi inline nell'HTML, ad esempio con
<button onclick="foo()"></button>
L'ambito delle variabili a cui un on*
attributo può fare riferimento deve essere:
querySelector
come indicherà una variabile autonoma document.querySelector
; raro)Altrimenti, verrà visualizzato un ReferenceError quando viene richiamato il gestore. Quindi, ad esempio, se il gestore inline fa riferimento a una funzione definita all'interno window.onload
o $(function() {
, il riferimento fallirà, perché il gestore inline può fare riferimento solo a variabili nell'ambito globale e la funzione non è globale:
Proprietà della document
e proprietà dell'elemento conduttore è collegato a può anche essere fatto riferimento come variabili indipendenti all'interno movimentatori linea perché movimentatori linea vengono richiamati all'interno di due with
blocchi , uno per la document
, uno per l'elemento. La catena di portata delle variabili all'interno di questi gestori è estremamente non intuitiva e un gestore di eventi di lavoro richiederà probabilmente una funzione per essere globale (e probabilmente dovrebbe essere evitato l'inquinamento globale non necessario ).
Poiché la catena dell'ambito all'interno dei gestori inline è così strana e poiché i gestori inline richiedono l'inquinamento globale per funzionare, e poiché i gestori inline a volte richiedono una brutta fuga di stringhe quando si passano gli argomenti, è probabilmente più facile evitarli. Invece, collega i gestori di eventi utilizzando Javascript (come con addEventListener
), anziché con markup HTML.
Su una nota diversa, a differenza dei normali <script>
tag, che vengono eseguiti al livello superiore, il codice all'interno dei moduli ES6 viene eseguito nel proprio ambito privato. Una variabile definita nella parte superiore di un <script>
tag normale è globale, quindi puoi fare riferimento ad altri <script>
tag, come questo:
Ma il livello superiore di un modulo ES6 non è globale. Una variabile dichiarata nella parte superiore di un modulo ES6 sarà visibile solo all'interno di quel modulo, a meno che la variabile non sia esplicitamente modificata export
o a meno che non sia assegnata a una proprietà dell'oggetto globale.
Il livello superiore di un modulo ES6 è simile a quello dell'interno di un IIFE al livello superiore in un normale <script>
. Il modulo può fare riferimento a tutte le variabili che sono globali e nulla può fare riferimento a nulla all'interno del modulo a meno che il modulo non sia esplicitamente progettato per esso.
In JavaScript ci sono due tipi di ambito:
La funzione Below ha una variabile di ambito locale carName
. E questa variabile non è accessibile dall'esterno della funzione.
function myFunction() {
var carName = "Volvo";
alert(carName);
// code here can use carName
}
La classe di seguito ha una variabile di ambito globale carName
. E questa variabile è accessibile da qualsiasi parte della classe.
class {
var carName = " Volvo";
// code here can use carName
function myFunction() {
alert(carName);
// code here can use carName
}
}
ES5
e precedenti:Le variabili in Javascript erano inizialmente (pre ES6
) con ambito lessicale funzionale. Il termine con ambito lessicale significa che è possibile visualizzare l'ambito delle variabili "osservando" il codice.
Ogni variabile dichiarata con la var
parola chiave è associata alla funzione. Tuttavia, se all'interno di tale funzione vengono dichiarate altre funzioni, tali funzioni avranno accesso alle variabili delle funzioni esterne. Questa si chiama catena di ambito . Funziona nel modo seguente:
// global scope
var foo = 'global';
var bar = 'global';
var foobar = 'global';
function outerFunc () {
// outerFunc scope
var foo = 'outerFunc';
var foobar = 'outerFunc';
innerFunc();
function innerFunc(){
// innerFunc scope
var foo = 'innerFunc';
console.log(foo);
console.log(bar);
console.log(foobar);
}
}
outerFunc();
Che cosa succede quando stiamo cercando di accedere alle variabili foo
, bar
e foobar
alla console è il seguente:
innerFunc
stessa. Pertanto, il valore di foo viene risolto nella stringa innerFunc
.innerFunc
stessa. Pertanto, dobbiamo scalare la catena di portata . Innanzitutto esaminiamo la funzione esterna in cui è innerFunc
stata definita la funzione . Questa è la funzione outerFunc
. Nell'ambito di outerFunc
possiamo trovare la barra delle variabili, che contiene la stringa 'outerFunc'.ES6
(ES 2015) e precedenti:Gli stessi concetti di lessical scope e scopechain si applicano ancora ES6
. Tuttavia, sono stati introdotti nuovi modi per dichiarare le variabili. Ci sono i seguenti:
let
: crea una variabile con ambito di bloccoconst
: crea una variabile con ambito di blocco che deve essere inizializzata e non può essere riassegnataLa più grande differenza tra var
e let
/ const
è che var
è nell'ambito della funzione mentre let
/ const
sono nell'ambito del blocco. Ecco un esempio per illustrare questo:
let letVar = 'global';
var varVar = 'global';
function foo () {
if (true) {
// this variable declared with let is scoped to the if block, block scoped
let letVar = 5;
// this variable declared with let is scoped to the function block, function scoped
var varVar = 10;
}
console.log(letVar);
console.log(varVar);
}
foo();
Nell'esempio precedente letVar registra il valore globale perché le variabili dichiarate con let
sono a ambito di blocco. Smettono di esistere al di fuori del rispettivo blocco, quindi non è possibile accedere alla variabile al di fuori del blocco if.
In EcmaScript5 esistono principalmente due ambiti, ambito locale e ambito globale, ma in EcmaScript6 abbiamo principalmente tre ambiti, ambito locale, ambito globale e un nuovo ambito chiamato ambito blocco .
Esempio di ambito di blocco è: -
for ( let i = 0; i < 10; i++)
{
statement1...
statement2...// inside this scope we can access the value of i, if we want to access the value of i outside for loop it will give undefined.
}
ECMAScript 6 ha introdotto le parole chiave let e const. Queste parole chiave possono essere utilizzate al posto della parola chiave var. Contrariamente alla parola chiave var, le parole chiave let e const supportano la dichiarazione di ambito locale all'interno delle istruzioni di blocco.
var x = 10
let y = 10
const z = 10
{
x = 20
let y = 20
const z = 20
{
x = 30
// x is in the global scope because of the 'var' keyword
let y = 30
// y is in the local scope because of the 'let' keyword
const z = 30
// z is in the local scope because of the 'const' keyword
console.log(x) // 30
console.log(y) // 30
console.log(z) // 30
}
console.log(x) // 30
console.log(y) // 20
console.log(z) // 20
}
console.log(x) // 30
console.log(y) // 10
console.log(z) // 10
Mi piace molto la risposta accettata ma voglio aggiungere questo:
Scope raccoglie e mantiene un elenco di ricerca di tutti gli identificatori dichiarati (variabili) e applica un rigido set di regole su come questi sono accessibili al codice attualmente in esecuzione.
Scope è un insieme di regole per la ricerca di variabili in base al nome dell'identificatore.
Esistono due tipi di ambiti in JavaScript.
Ambito globale : la variabile annunciata in ambito globale può essere utilizzata in modo uniforme in qualsiasi parte del programma. Per esempio:
var carName = " BMW";
// code here can use carName
function myFunction() {
// code here can use carName
}
Ambito funzionale o ambito locale : la variabile dichiarata in questo ambito può essere utilizzata solo nella propria funzione. Per esempio:
// code here can not use carName
function myFunction() {
var carName = "BMW";
// code here can use carName
}