2016-05-27 33 views
26

Ho scritto questo codice per insegnare a me stesso su chiusure JavaScript:Dove vive una chiusura JavaScript?

function1 = function(){ 
    var variable = "foo" 
    var function2 = function(argument){ 
    console.log(variable + argument); 
    } 
    return function2 
} 

function3 = function1(); 
function3("bar"); 

Questa stampe "foobar", come previsto. Ma dove vive la variabile?

Diventa una proprietà di function3 o memorizzata da qualche altra parte in function3? JavaScript attraversa una sorta di catena di chiusura, in modo simile a come attraversa la catena del prototipo? È archiviato in memoria da qualche altra parte?

Sto cercando di capire questo più profondamente.

+1

Sì, è possibile confrontare questo con il prototipo-catena, tipo di, dove ogni nodo memorizza le variabili e i loro valori per questa particolare chiamata-funzione (importante: quella particolare chiamata-funzione, non solo la funzione), fino a lo scopo globale. È lo stesso meccanismo che ti consente di accedere a variabili da un ambito esterno, se non sono definite nel tuo ambito corrente. MA: non c'è possibilità di stampare/vedere/accedere o modificare questa cosa. È un costrutto dietro le quinte del tuo motore. – Thomas

+1

possibile duplicato di [Come funzionano le chiusure JavaScript a un livello basso?] (Http://stackoverflow.com/q/31735129/1048572) o [Come le chiusure dei jerk sono raccolte illeggibili] (http://stackoverflow.com/q/19798803/1048572)? Vedi anche [Dove si trova la variabile nella chiusura memorizzata, stack o heap?] (Http://stackoverflow.com/q/29225834/1048572) e [chiusure Javascript su heap o stack?] (Http://stackoverflow.com)/q/16959342/1,048572 millions). – Bergi

risposta

30

tl; dr:

where does the variable live?

Nell'ambiente è stato definito in

Does it become a property of function3, or stored somewhere else in function3?

No.

Does JavaScript traverse some kind of closure chain, similarly to how it traverses the prototype chain?

Sì..

Is it stored in memory somewhere else?

Sì.


tl; dr. 2:

Funzioni mantenere un riferimento all'ambiente in cui vengono creati in Quando una funzione viene chiamata crea un nuovo ambiente il cui genitore è l'ambiente la funzione mantenuto il riferimento a.


più lunga spiegazione:

Ogni volta che viene eseguita una funzione di un nuovo lexical environment viene creato. L'ambiente ha due "campi": uno environment record in cui vengono tracciate tutte le variabili e un ambiente lessicale che fa riferimento, come suggerisce il nome, all '"ambiente lessicale genitore".

Così, quando il vostro esempio di codice viene valutata, lo stato iniziale della memoria (prima di eseguire qualsiasi cosa) potrebbe essere simile a questo (semplificato):

+-(Global) lexical environment-+  +-Environment Record-+ 
+-------------+----------------+  +---------+----------+ 
| Environment |  *--------+---> |function1|undefined | 
| Record |    |  +---------+----------+ 
+-------------+----------------+  |function3|undefined | 
| Outer |    |  +---------+----------+ 
| lexical | (empty)  | 
| environment |    | 
+-------------+----------------+ 

L'ambiente globale non dispone qualsiasi ambiente esterno perché è in cima. function1 e function3 sono due collegamenti non ancora inizializzati (l'assegnazione non è stata ancora valutata).

Dopo aver creato la funzione (valutare function1 = function() { ... }), la memoria aspetto:

  +------------------------------------------------------------------------+ 
      |                  | 
      v                  | 
+-(Global) lexical environment-+ +-Environment Record-+  +-----Function Object-+---+ 
+-------------+----------------+ +---------+----------+  +---------------+-----+---+ 
| Environment |  *--------+--->|function1| *-----+---->|[[Environment]]|  * | 
| Record |    | +---------+----------+  +---------------+---------+ 
+-------------+----------------+ |function3|undefined |  |  name  |function1| 
| Outer |    | +---------+----------+  +---------------+---------+ 
| lexical | (empty)  | 
| environment |    | 
+-------------+----------------+ 

Ora function1 ha un valore, un oggetto funzione. Gli oggetti funzione hanno proprietà interne multiple (ad esempio [[Environment]]) ed esterne (ad esempio name). Come suggerisce il nome, non è possibile accedere alle proprietà interne dal codice utente. La proprietà [[Environment]] è molto importante.Nota come si fa riferimento all'ambiente lessicale in cui è stata creata la funzione!

Il passaggio successivo è in esecuzione function3 = function1(), ovvero chiamando function2. Come ho detto all'inizio, ogni volta che viene eseguita una funzione viene creato un nuovo ambiente lessicale. Diamo un'occhiata alla memoria subito dopo aver inserito la funzione:

   +------------------------------------------------------------------------+ 
       |                  | 
       v                  | 
    +-(Global) lexical environment-+ +-Environment Record-+  +-----Function Object-+---+ 
    +-------------+----------------+ +---------+----------+  +---------------+-----+---+ 
    | Environment |  *--------+--->|function1|   +---->|[[Environment]]|  * | 
    | Record |    | +---------+----------+  +---------------+---------+ 
+> +-------------+----------------+ |function3|undefined |  |  name  |function1| 
| | Outer |    | +---------+----------+  +---------------+---------+ 
| | lexical | (empty)  | 
| | environment |    | 
| +-------------+----------------+ 
| 
| 
| 
| +-----lexical environment------+ +-Environment Record-+ 
| +-------------+----------------+ +---------+----------+ 
| | Environment |  *--------+--->|variable |undefined | 
| | Record |    | +---------+----------+ 
| +-------------+----------------+ |function2|undefined | 
| | Outer |    | +---------+----------+ 
| | lexical |  *  | 
| | environment |  |  | 
| +-------------+--------+-------+ 
|       | 
+-------------------------+ 

Questo sembra molto simile alla struttura dell'ambiente globale! Abbiamo un ambiente lessicale con un record di ambiente con due binding non personalizzati. Ma la grande differenza ora è che "l'ambiente lessicale esterno" punta all'ambiente lessicale globale. Come è possibile?

Quando si chiama function1 e si crea un nuovo ambiente lessicale, si imposta il valore del campo "ambiente lessicale esterno" dei nuovi ambienti sul valore del campo [[Environment]]function1. Questo è il caso in cui è stata creata la catena di portata .

Ora, dopo l'esecuzione function1, la memoria ha questa struttura:

   +------------------------------------------------------------------------+ 
       |                  | 
       v                  | 
    +-(Global) lexical environment-+ +-Environment Record-+  +-----Function Object-+---+ 
    +-------------+----------------+ +---------+----------+  +---------------+-----+---+ 
    | Environment |  *--------+--->|function1| *-----+---->|[[Environment]]|  * | 
    | Record |    | +---------+----------+  +---------------+---------+ 
+> +-------------+----------------+ |function3| |  |  |  name  |function1| 
| | Outer |    | +---------+---+------+  +---------------+---------+ 
| | lexical | (empty)  |     | 
| | environment |    |     | 
| +-------------+----------------+     +-------------------------+ 
|                    | 
|    +----------------------------------------------------------------+--------+ 
|    v                |  | 
| +-----lexical environment------+ +-Environment Record-+     v  | 
| +-------------+----------------+ +---------+----------+       | 
| | Environment |  *--------+--->|variable | 'foo' |  +-----Function Object-+---+ 
| | Record |    | +---------+----------+  +---------------+-----+---+ 
| +-------------+----------------+ |function2| *-----+---->|[[Environment]]|  * | 
| | Outer |    | +---------+----------+  +---------------+---------+ 
| | lexical |  *  |        |  name  |function2| 
| | environment |  |  |        +---------------+---------+ 
| +-------------+--------+-------+ 
|       | 
+-------------------------+ 

simili come function1, function2 ha un riferimento per l'ambiente creato da chiamando function2. Inoltre, function3 fa riferimento alla funzione che abbiamo creato perché la restituiamo da function1.

Ultimo passo: chiamando function3('bar'):

   +------------------------------------------------------------------------+ 
       |                  | 
       v                  | 
    +-(Global) lexical environment-+ +-Environment Record-+  +-----Function Object-+---+ 
    +-------------+----------------+ +---------+----------+  +---------------+-----+---+ 
    | Environment |  *--------+--->|function1| *-----+---->|[[Environment]]|  * | 
    | Record |    | +---------+----------+  +---------------+---------+ 
+> +-------------+----------------+ |function3| |  |  |  name  |function1| 
| | Outer |    | +---------+---+------+  +---------------+---------+ 
| | lexical | (empty)  |     | 
| | environment |    |     | 
| +-------------+----------------+     +-------------------------+ 
|                    | 
|    +----------------------------------------------------------------+--------+ 
|    v                |  | 
| +-----lexical environment------+ +-Environment Record-+     v  | 
| +-------------+----------------+ +---------+----------+       | 
| | Environment |  *--------+--->|variable | 'foo' |  +-----Function Object-+---+ 
| | Record |    | +---------+----------+  +---------------+-----+---+ 
|+>+-------------+----------------+ |function2| *-----+---->|[[Environment]]|  * | 
|| | Outer |    | +---------+----------+  +---------------+---------+ 
|| | lexical |  *  |        |  name  |function2| 
|| | environment |  |  |        +---------------+---------+ 
|| +-------------+--------+-------+ 
++------------------------+ 
| 
| +-----lexical environment------+ +-Environment Record-+ 
| +-------------+----------------+ +---------+----------+ 
| | Environment |  *--------+--->|argument | 'bar' | 
| | Record |    | +---------+----------+ 
| +-------------+----------------+ 
| | Outer |    | 
| | lexical |  *  | 
| | environment |  |  | 
| +-------------+--------+-------+ 
+------------------------+ 

simile qui, un nuovo ambiente è stato creato e le sue "ambiente esterno lessicale" punti sul campo per l'ambiente creato quando function1 è stato chiamato.

Ora, cercare il valore di argument è semplice, perché esiste nel record proprio dell'ambiente. Ma quando si guarda su variable, accade quanto segue: Poiché non esiste nel record proprio dell'ambiente, guarda al suo record "ambiente lessicale esterno". Può farlo perché ha un riferimento ad esso.

+0

Chiarimento: dopo che 'function1' è definita ma prima che sia eseguita, sono il record di ambiente che contiene' variable' e 'function2' e la lessica esterna l ambiente che fa riferimento a Global memorizzato in memoria o no fino a quando 'function1' viene effettivamente chiamato sulla riga 9? Se ti sto seguendo correttamente, non vengono memorizzati finché non viene chiamato 'function1', a quel punto' function3' è ora definito nello spazio dei nomi globale. Questo ha senso per me. Ho ragione? –

+1

Il secondo record di ambiente viene creato solo quando viene chiamato 'function1'. 'function3' è solo definito * dopo che *' function1' è stato eseguito (e restituito il valore). –

+3

c'è qualche strumento speciale che usi per disegnare questi diagrammi ASCII? – Random832

2

Le variabili vivono nello scope in cui sono dichiarate, che è globale o una funzione.

La parola chiave qui è scope.

Come explained brilliantly nel sito Web MSDN:

A variable that is declared inside a function definition is local. It is created and destroyed every time the function is executed, and it cannot be accessed by any code outside the function. JavaScript does not support block scope (in which a set of braces {. . .} defines a new scope), except in the special case of block-scoped variables.

EDIT:

In realtà è un po 'più complicato di questo, vedere toddmotto s' post su ambiti di JS.

+1

Copia questo dal link toddmotto perché questo è molto rilevante: "Le catene di scope stabiliscono lo scopo di una data funzione. Ogni funzione definita ha il proprio ambito nidificato come sappiamo, e qualsiasi funzione definita all'interno di un'altra funzione ha un ambito locale che è collegato alla funzione esterna - questo collegamento è chiamato catena. È sempre la posizione nel codice che definisce l'ambito. Quando si risolve una variabile, JavaScript parte dall'ambito più interno e ricerca all'esterno finché non trova la variabile/oggetto/funzione che stava cercando. " – Renato

+0

" JavaScript non supporta l'ambito dei blocchi (in cui un insieme di parentesi graffe {..}} Definisce un nuovo ambito), tranne nel caso particolare delle variabili con scope a blocchi." Cosa significa? Il blocco all'interno di {..}} Che definisce' function1' è un ambito locale giusto? E al di fuori di quel blocco è l'ambito globale giusto? Non capisco cosa stia dicendo questa frase –

+0

" Una variabile dichiarata all'interno di una definizione di funzione è locale. Viene creato e distrutto ogni volta che la funzione viene eseguita e non è accessibile da alcun codice esterno alla funzione. " Tranne che non è proprio vero a causa della chiusura.' Function3' esiste interamente al di fuori di 'function1', eppure può accedere alle variabili definite in 'function1'. La domanda è, come accede a quella variabile? Dove vive la variabile nel frattempo? Non è definita in ambito globale. Se provo a chiamare variabile nello scope globale ottengo' ReferenceError: variable is not defined. –

1

Per una spiegazione su come funzionano le chiusure, vedere questa risposta.

How do JavaScript closures work?

A livello di macchina virtuale, ogni funzione ha la propria lexical environment che tiene traccia di queste informazioni. La macchina virtuale trova quali variabili sono accessibili nelle chiusure e le memorizza nell'heap e vivono fino a quando una chiusura potrebbe averne bisogno.

Per ulteriori approfondimenti si veda per esempio questi due grandi pezzi:

+0

Per la cronaca, ho letto questa risposta SO prima di pubblicare questa domanda –

4

Ogni volta JavaScript esegue la funzione funzione3, un oggetto 'scope' è c raggiato per contenere la variabile locale chiamata da te come variabile ("pippo"). Nota che il tuo codice JavaScript non può accedere direttamente a questo oggetto scope. E quindi il valore "pippo" è disponibile per la funzione interiore, sebbene la funzione esterna sia ritornata.

Does JavaScript traverse some kind of closure chain, similarly to how it traverses the prototype chain?

Sì."Gli oggetti di ambito formano una catena chiamata catena dell'ambito, simile alla catena di prototipi utilizzata dal sistema di oggetti di JavaScript

Una chiusura è la combinazione di una funzione e l'oggetto ambito in cui è stata creata. come tali, spesso possono essere utilizzati al posto degli oggetti"

per saperne di più qui:

+0

Una cosa su cui sono confuso è se le definizioni delle variabili sono garbage collection quando si esce da un ambito. Ho imparato prima Ruby e Non so se le variabili sono spazzatura raccolte quando si esce dall'ambito, ma si comportano come sono. Qualsiasi chiarimento sul fatto che 'variable' è garbage collection e quindi riattivato quando necessario, o memorizzato in memoria anche in ambito globale? –

+0

"Ogni volta che JavaScript esegue una funzione, viene creato un oggetto 'ambito' per contenere le variabili locali create all'interno di quella funzione. Viene inizializzato con qualsiasi variabile passata come parametri di funzione. Questo è simile all'oggetto globale in cui vivono tutte le variabili e le funzioni globali, ma "..." viene creato un nuovo oggetto di ambito ogni volta che una funzione inizia a eseguire "OK, in modo che risulti come se le variabili fossero effettivamente raccolte dalla garbage collection e nuovamente istanziato quando necessario, nel qual caso il blocco di memoria che memorizza la funzione anonima 'function3' deve includere il suo ambito. –

+0

@WylliamJudd L'oggetto scope che rimane intorno è l'oggetto scope della funzione _parent_ (' function1' nel tuo esempio), non la funzione di chiusura stessa, ne viene creata una nuova ad ogni esecuzione per qualsiasi variabile locale (inclusi gli argomenti) dichiarata _con la funzione.Quando funzione1 restituisce due o più funzioni faranno riferimento allo stesso oggetto scope di funzione1 per la loro chiusura – Random832

Problemi correlati