Come funzionano le chiusure JavaScript?


7636

Come spiegheresti le chiusure JavaScript a qualcuno con una conoscenza dei concetti in cui consistono (ad esempio funzioni, variabili e simili), ma non capisce le chiusure stesse?

Ho visto l'esempio di Schema fornito su Wikipedia, ma sfortunatamente non ha aiutato.


391
Il mio problema con queste e molte risposte è che si avvicinano da una prospettiva astratta e teorica, piuttosto che iniziare spiegando semplicemente perché le chiusure sono necessarie in Javascript e le situazioni pratiche in cui le usi. Si finisce con un articolo tl; dr che bisogna sfogliare, pensando continuamente, "ma, perché?". Vorrei semplicemente iniziare: le chiusure sono un modo pulito per gestire le seguenti due realtà di JavaScript: a. l'ambito è a livello di funzione, non a livello di blocco e, b. gran parte di ciò che fai in pratica in JavaScript è asincrono / guidato dagli eventi.
Jeremy Burton

53
@Redsandro Per prima cosa, rende molto più facile scrivere codice guidato dagli eventi. Potrei attivare una funzione quando la pagina viene caricata per determinare specifiche sull'HTML o sulle funzionalità disponibili. Posso definire e impostare un gestore in quella funzione e avere tutte le informazioni sul contesto disponibili ogni volta che viene chiamato il gestore senza doverlo interrogare nuovamente. Risolvi il problema una volta, riutilizzalo su ogni pagina in cui quel gestore è necessario con un overhead ridotto sulla re-invocazione del gestore. Hai mai visto gli stessi dati essere ri-mappati due volte in una lingua che non li ha? Le chiusure rendono molto più facile evitare questo genere di cose.
Erik Reppen,

1
@Erik Reppen grazie per la risposta. In realtà, ero curioso dei vantaggi di questo closurecodice difficile da leggere , al contrario del Object Literalquale si riutilizza e riduce lo stesso sovraccarico, ma richiede il 100% di codice di wrapping in meno.
Redsandro,

6
Per i programmatori Java, la risposta breve è che è la funzione equivalente di una classe interna. Una classe interna contiene anche un puntatore implicito a un'istanza della classe esterna e viene utilizzata per lo stesso scopo (ovvero la creazione di gestori di eventi).
Boris van Schooten,

8
Ho trovato questo esempio pratico molto utile: youtube.com/watch?v=w1s9PgtEoJs
Abhi

Risposte:


7360

Una chiusura è un'associazione di:

  1. Una funzione e
  2. Un riferimento all'ambito esterno di quella funzione (ambiente lessicale)

Un ambiente lessicale fa parte di ogni contesto di esecuzione (stack frame) ed è una mappa tra identificatori (ovvero nomi di variabili locali) e valori.

Ogni funzione in JavaScript mantiene un riferimento al suo ambiente lessicale esterno. Questo riferimento viene utilizzato per configurare il contesto di esecuzione creato quando viene invocata una funzione. Questo riferimento consente al codice all'interno della funzione di "vedere" le variabili dichiarate all'esterno della funzione, indipendentemente da quando e dove viene chiamata la funzione.

Se una funzione è stata chiamata da una funzione, che a sua volta è stata chiamata da un'altra funzione, viene creata una catena di riferimenti ad ambienti lessicali esterni. Questa catena è chiamata catena dell'ambito.

Nel codice seguente, innerforma una chiusura con l'ambiente lessicale del contesto di esecuzione creato quando fooviene invocato, chiudendo su variabile secret:

function foo() {
  const secret = Math.trunc(Math.random()*100)
  return function inner() {
    console.log(`The secret number is ${secret}.`)
  }
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`

In altre parole: in JavaScript, le funzioni portano un riferimento a una "scatola di stato" privata, alla quale solo loro (e tutte le altre funzioni dichiarate all'interno dello stesso ambiente lessicale) hanno accesso. Questa casella di stato è invisibile al chiamante della funzione, offrendo un eccellente meccanismo per nascondere e incapsulare i dati.

E ricorda: le funzioni in JavaScript possono essere passate come variabili (funzioni di prima classe), il che significa che questi accoppiamenti di funzionalità e stato possono essere passati al tuo programma: simile a come potresti passare un'istanza di una classe in C ++.

Se JavaScript non avesse chiusure, sarebbe necessario passare esplicitamente più stato tra le funzioni , rendendo più lunghi gli elenchi di parametri e il codice più rumoroso.

Pertanto, se si desidera che una funzione abbia sempre accesso a uno stato privato, è possibile utilizzare una chiusura.

... e spesso ci facciamo vogliamo stato associato con una funzione. Ad esempio, in Java o C ++, quando si aggiunge una variabile di istanza privata e un metodo a una classe, si associa lo stato alla funzionalità.

In C e nella maggior parte degli altri linguaggi comuni, dopo il ritorno di una funzione, tutte le variabili locali non sono più accessibili perché lo stack-frame viene distrutto. In JavaScript, se si dichiara una funzione all'interno di un'altra funzione, le variabili locali della funzione esterna possono rimanere accessibili dopo il ritorno da essa. In questo modo, nel codice sopra, secretrimane disponibile per l'oggetto funzione inner, dopo che è stato restituito foo.

Usi di chiusure

Le chiusure sono utili ogni volta che è necessario uno stato privato associato a una funzione. Questo è uno scenario molto comune - e ricorda: JavaScript non aveva una sintassi della classe fino al 2015 e non ha ancora una sintassi del campo privato. Le chiusure soddisfano questa esigenza.

Variabili di istanze private

Nel seguente codice, la funzione si toStringchiude sui dettagli dell'auto.

function Car(manufacturer, model, year, color) {
  return {
    toString() {
      return `${manufacturer} ${model} (${year}, ${color})`
    }
  }
}
const car = new Car('Aston Martin','V8 Vantage','2012','Quantum Silver')
console.log(car.toString())

Programmazione Funzionale

Nel codice seguente, la funzione si innerchiude su entrambi fne args.

function curry(fn) {
  const args = []
  return function inner(arg) {
    if(args.length === fn.length) return fn(...args)
    args.push(arg)
    return inner
  }
}

function add(a, b) {
  return a + b
}

const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5

Programmazione orientata agli eventi

Nel codice seguente, la funzione si onClickchiude sulla variabile BACKGROUND_COLOR.

const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200,200,242,1)'

function onClick() {
  $('body').style.background = BACKGROUND_COLOR
}

$('button').addEventListener('click', onClick)
<button>Set background color</button>

La modularizzazione

Nel seguente esempio, tutti i dettagli dell'implementazione sono nascosti all'interno di un'espressione di funzione eseguita immediatamente. Le funzioni ticke la toStringchiusura sopra lo stato privato e le funzioni necessarie per completare il loro lavoro. Le chiusure ci hanno permesso di modularizzare e incapsulare il nostro codice.

let namespace = {};

(function foo(n) {
  let numbers = []
  function format(n) {
    return Math.trunc(n)
  }
  function tick() {
    numbers.push(Math.random() * 100)
  }
  function toString() {
    return numbers.map(format)
  }
  n.counter = {
    tick,
    toString
  }
}(namespace))

const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())

Esempi

Esempio 1

Questo esempio mostra che le variabili locali non vengono copiate nella chiusura: la chiusura mantiene un riferimento alle variabili originali stesse . È come se lo stack-frame rimanesse vivo in memoria anche dopo l'uscita dalla funzione esterna.

function foo() {
  let x = 42
  let inner  = function() { console.log(x) }
  x = x+1
  return inner
}
var f = foo()
f() // logs 43

Esempio 2

Nel seguente codice, tre metodi log, incremente updatetutti vicini nello stesso ambiente lessicale.

E ogni volta che createObjectviene chiamato, viene creato un nuovo contesto di esecuzione (stack frame) e viene creata una variabile completamente nuova x, e viene creato un nuovo set di funzioni ( logecc.), Che si chiudono su questa nuova variabile.

function createObject() {
  let x = 42;
  return {
    log() { console.log(x) },
    increment() { x++ },
    update(value) { x = value }
  }
}

const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

Esempio 3

Se stai usando le variabili dichiarate usando var, fai attenzione a capire su quale variabile stai chiudendo. Le variabili dichiarate utilizzando varvengono sollevate. Questo è molto meno un problema nel moderno JavaScript a causa dell'introduzione di lete const.

Nel codice seguente, ogni volta intorno al ciclo, innerviene creata una nuova funzione , che si chiude i. Ma poiché var iviene sollevato al di fuori del ciclo, tutte queste funzioni interne si chiudono sulla stessa variabile, il che significa che il valore finale di i(3) viene stampato, tre volte.

function foo() {
  var result = []
  for (var i = 0; i < 3; i++) {
    result.push(function inner() { console.log(i) } )
  }
  return result
}

const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
  result[i]() 
}

Punti finali:

  • Ogni volta che una funzione viene dichiarata in JavaScript viene creata una chiusura.
  • Restituzione a function da dentro un'altra funzione è il classico esempio di chiusura, perché lo stato all'interno della funzione esterna è implicitamente disponibile per la funzione interna restituita, anche dopo che la funzione esterna ha completato l'esecuzione.
  • Ogni volta che si utilizza eval()all'interno di una funzione, viene utilizzata una chiusura. Nel testo è evalpossibile fare riferimento a variabili locali della funzione e in modalità non rigorosa è anche possibile creare nuove variabili locali utilizzandoeval('var foo = …') .
  • Quando si utilizza new Function(…)(il costruttore della funzione ) all'interno di una funzione, questa non si chiude sul suo ambiente lessicale: si chiude invece sul contesto globale. La nuova funzione non può fare riferimento alle variabili locali della funzione esterna.
  • Una chiusura in JavaScript è come mantenere un riferimento ( NON una copia) all'ambito nel punto di dichiarazione della funzione, che a sua volta mantiene un riferimento al suo ambito esterno, e così via, fino all'oggetto globale nella parte superiore di la catena di portata.
  • Una chiusura viene creata quando viene dichiarata una funzione; questa chiusura viene utilizzata per configurare il contesto di esecuzione quando viene invocata la funzione.
  • Un nuovo set di variabili locali viene creato ogni volta che viene chiamata una funzione.

link


74
Questo suona bene: "Una chiusura in JavaScript è come conservare una copia di tutte le variabili locali, proprio come quando usciva una funzione". Ma è fuorviante per un paio di ragioni. (1) La chiamata di funzione non deve uscire per creare una chiusura. (2) Non è una copia dei valori delle variabili locali ma delle variabili stesse. (3) Non dice chi ha accesso a queste variabili.
dlaliberte,

27
L'esempio 5 mostra un "gotcha" in cui il codice non funziona come previsto. Ma non mostra come risolverlo. Questa altra risposta mostra un modo per farlo.
Matt,

190
Mi piace come questo post inizi con grandi lettere in grassetto che dicono "Closures Are Not Magic" e termina il suo primo esempio con "La magia è che in JavaScript un riferimento di funzione ha anche un riferimento segreto alla chiusura in cui è stato creato".
Andrew Macheret,

6
L'esempio n. 3 sta mescolando le chiusure con il sollevamento di javagram. Ora penso che spiegare solo le chiusure sia abbastanza difficile senza introdurre il comportamento di sollevamento. Questo mi ha aiutato di più: Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created.da developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
caramba

3
ECMAScript 6 potrebbe cambiare qualcosa in questo fantastico articolo sulla chiusura. Ad esempio, se si utilizza let i = 0anziché var i = 0nell'esempio 5, testList()verrà stampato ciò che si desidera in origine.
Nier,

3989

Ogni funzione in JavaScript mantiene un collegamento al suo ambiente lessicale esterno. Un ambiente lessicale è una mappa di tutti i nomi (ad es. Variabili, parametri) all'interno di un ambito, con i loro valori.

Quindi, ogni volta che vedi la functionparola chiave, il codice all'interno di quella funzione ha accesso alle variabili dichiarate al di fuori della funzione.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Ciò verrà registrato 16perché la funzione si barchiude sul parametro xe sulla variabile tmp, entrambi presenti nell'ambiente lessicale della funzione esternafoo .

La funzione bar, insieme al suo legame con l'ambiente lessicale della funzione, fooè una chiusura.

Una funzione non deve tornare per creare una chiusura. Semplicemente in virtù della sua dichiarazione, ogni funzione si chiude sul suo ambiente lessicale racchiuso, formando una chiusura.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2);
bar(10); // 16
bar(10); // 17

La funzione precedente registra anche 16, perché il codice all'interno barpuò ancora fare riferimento a argomento xe variabiletmp , anche se non sono più direttamente nell'ambito.

Tuttavia, poiché tmpè ancora sospeso all'interno bardella chiusura, è disponibile per essere incrementato. Sarà incrementato ogni volta che chiamibar .

L'esempio più semplice di una chiusura è questo:

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Quando viene invocata una funzione JavaScript, ecviene creato un nuovo contesto di esecuzione . Insieme agli argomenti della funzione e all'oggetto target, questo contesto di esecuzione riceve anche un collegamento all'ambiente lessicale del contesto di esecuzione chiamante, il che significa che le variabili dichiarate nell'ambiente lessicale esterno (nell'esempio precedente, sia ae sia b) sono disponibili da ec.

Ogni funzione crea una chiusura perché ogni funzione ha un collegamento al suo ambiente lessicale esterno.

Si noti che le variabili stesse sono visibili all'interno di una chiusura, non copie.


24
@feeela: Sì, ogni funzione JS crea una chiusura. Le variabili a cui non viene fatto riferimento saranno probabilmente rese idonee per la garbage collection nei moderni motori JS, ma ciò non cambia il fatto che quando si crea un contesto di esecuzione, quel contesto ha un riferimento al contesto di esecuzione incluso e alle sue variabili e quella funzione è un oggetto che ha il potenziale per essere trasferito in un diverso ambito variabile, pur mantenendo quel riferimento originale. Questa è la chiusura.

@Ali Ho appena scoperto che il jsFiddle che ho fornito in realtà non dimostra nulla, dal momento che deletefallisce. Tuttavia, l'ambiente lessicale che la funzione porterà come [[Scope]] (e alla fine userà come base per il proprio ambiente lessicale quando invocato) viene determinato quando viene eseguita l'istruzione che definisce la funzione. Ciò significa che la funzione si sta chiudendo su TUTTI i contenuti dell'ambito di esecuzione, indipendentemente dai valori a cui si riferisce effettivamente e se sfugge all'ambito. Si prega di guardare le sezioni 13.2 e 10 nelle specifiche
Asad Saeeduddin

8
Questa è stata una buona risposta fino a quando non ha provato a spiegare tipi e riferimenti primitivi. Si sbaglia completamente e parla del fatto che i letterali vengono copiati, il che non ha nulla a che fare con nulla.
Ry-

12
Le chiusure sono la risposta di JavaScript alla programmazione basata su classi e orientata agli oggetti. JS non è basato sulla classe, quindi è stato necessario trovare un altro modo per implementare alcune cose che altrimenti non potrebbero essere implementate.
Bartłomiej Zalewski,

2
questa dovrebbe essere la risposta accettata. La magia non accade mai nella funzione interiore. Succede quando si assegna la funzione esterna a una variabile. Questo crea un nuovo contesto di esecuzione per la funzione interna, quindi la "variabile privata" può essere accumulata. Certamente può dal momento che la variabile alla quale è stata assegnata la funzione esterna ha mantenuto il contesto. La prima risposta rende l'intera faccenda più complessa senza spiegare cosa succede realmente lì.
Albert Gao,

2442

PREFAZIONE: questa risposta è stata scritta quando la domanda era:

Come ha detto il vecchio Albert: "Se non riesci a spiegarlo a un bambino di sei anni, non lo capisci davvero da solo". Beh, ho cercato di spiegare le chiusure di JS a un amico di 27 anni e ho completamente fallito.

Qualcuno può considerare che io sono 6 e stranamente interessato a quell'argomento?

Sono abbastanza sicuro di essere stata una delle uniche persone che hanno tentato di rispondere alla domanda iniziale alla lettera. Da allora, la domanda è mutata più volte, quindi la mia risposta può ora sembrare incredibilmente sciocca e fuori posto. Spero che l'idea generale della storia rimanga divertente per alcuni.


Sono un grande fan dell'analogia e della metafora quando spiego concetti difficili, quindi lasciami provare con una storia.

C'era una volta:

C'era una principessa ...

function princess() {

Ha vissuto in un mondo meraviglioso pieno di avventure. Incontrò il suo Principe Azzurro, cavalcò il suo mondo su un unicorno, combatté draghi, incontrò animali parlanti e molte altre cose fantastiche.

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

Ma avrebbe sempre dovuto tornare al suo noioso mondo di faccende e adulti.

    return {

E spesso raccontava loro la sua ultima fantastica avventura da principessa.

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Ma vedrebbero solo una bambina ...

var littleGirl = princess();

... raccontando storie di magia e fantasia.

littleGirl.story();

E anche se gli adulti conoscessero le vere principesse, non avrebbero mai creduto negli unicorni o nei draghi perché non potevano mai vederli. Gli adulti hanno detto che esistevano solo nell'immaginazione della bambina.

Ma conosciamo la vera verità; che la bambina con la principessa dentro ...

... è davvero una principessa con dentro una bambina.


340
Adoro questa spiegazione, davvero. Per coloro che lo leggono e non seguono, l'analogia è questa: la funzione princess () è un ambito complesso che contiene dati privati. Al di fuori della funzione, non è possibile visualizzare o accedere ai dati privati. La principessa mantiene gli unicorni, i draghi, le avventure ecc. Nella sua immaginazione (dati privati) e gli adulti non possono vederli da soli. MA l'immaginazione della principessa viene catturata nella chiusura per ilstory() funzione, che è l'unica interfaccia che l' littleGirlistanza espone nel mondo della magia.
Patrick M,

Ecco story la chiusura ma se il codice fosse stato var story = function() {}; return story;allora littleGirlsarebbe la chiusura. Almeno questa è l'impressione che ottengo dall'uso di MDN di metodi "privati" con le chiusure : "Queste tre funzioni pubbliche sono chiusure che condividono lo stesso ambiente".
icc97,

16
@ icc97, sì, storyè una chiusura che fa riferimento all'ambiente fornito nell'ambito di princess. princessè anche un altro implicito chiusura , vale a dire che princesse littleGirlcondividerebbe qualsiasi riferimento a un parentsarray che esisterebbe nuovamente nell'ambiente / ambito in cui littleGirlesiste e princessè definito.
Jacob Swartwood

6
@BenjaminKrupp Ho aggiunto un commento in codice esplicito per mostrare / implicare che ci sono più operazioni all'interno del corpo di princess quello che è scritto. Sfortunatamente questa storia è ora un po 'fuori posto in questa discussione. Inizialmente la domanda era "spiegare le chiusure JavaScript a un bambino di 5 anni"; la mia risposta è stata l'unica che ha persino tentato di farlo. Non dubito che sarebbe fallito miseramente, ma almeno questa risposta avrebbe potuto avere la possibilità di mantenere l'interesse di un bambino di 5 anni.
Jacob Swartwood,

11
In realtà, per me questo ha perfettamente senso. E devo ammetterlo, capire finalmente una chiusura di JS usando storie di principesse e avventure mi fa sentire un po 'strano.
Cristallizza

753

Prendendo sul serio la domanda, dovremmo scoprire di cosa è capace un bambino di 6 anni in modo cognitivo, anche se è vero, chi è interessato a JavaScript non è così tipico.

Sullo sviluppo dell'infanzia: dai 5 ai 7 anni dice:

Il tuo bambino sarà in grado di seguire le indicazioni in due passaggi. Ad esempio, se dici a tuo figlio, "Vai in cucina e prendimi un sacco della spazzatura", saranno in grado di ricordare quella direzione.

Possiamo usare questo esempio per spiegare le chiusure, come segue:

La cucina è una chiusura che ha una variabile locale, chiamata trashBags. C'è una funzione all'interno della cucina chiamata getTrashBagche ottiene un sacchetto della spazzatura e lo restituisce.

Possiamo codificarlo in JavaScript in questo modo:

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

Ulteriori punti che spiegano perché le chiusure sono interessanti:

  • Ogni volta che makeKitchen()viene chiamato, viene creata una nuova chiusura con un proprio separato trashBags.
  • La trashBagsvariabile è locale all'interno di ogni cucina e non è accessibile all'esterno, ma la funzione interna sulla getTrashBagproprietà ha accesso ad essa.
  • Ogni chiamata di funzione crea una chiusura, ma non sarebbe necessario tenerla chiusa a meno che una funzione interna, che ha accesso all'interno della chiusura, possa essere chiamata dall'esterno della chiusura. Restituire l'oggetto con la getTrashBagfunzione lo fa qui.

6
In realtà, in modo confuso, la chiamata alla funzione makeKitchen è la chiusura effettiva, non l'oggetto da cucina che restituisce.
dlaliberte,

6
Facendo strada tra gli altri ho trovato questa risposta come il modo più semplice per spiegare cosa e perché i closures.is.
Chetabahana,

3
Troppo menu e antipasto, non abbastanza carne e patate. Potresti migliorare quella risposta con una sola breve frase del tipo: "Una chiusura è il contesto sigillato di una funzione, per mancanza di qualsiasi meccanismo di scoping fornito dalle classi".
Staplerfahrer,

584

The Straw Man

Devo sapere quante volte è stato fatto clic su un pulsante e fare qualcosa ogni terzo clic ...

Soluzione abbastanza ovvia

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

Ora funzionerà, ma invaderà l'ambito esterno aggiungendo una variabile, il cui unico scopo è tenere traccia del conteggio. In alcune situazioni, ciò sarebbe preferibile in quanto l'applicazione esterna potrebbe richiedere l'accesso a queste informazioni. Ma in questo caso, stiamo solo cambiando il comportamento di ogni terzo clic, quindi è preferibile racchiudere questa funzionalità all'interno del gestore eventi .

Considera questa opzione

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Nota alcune cose qui.

Nell'esempio sopra, sto usando il comportamento di chiusura di JavaScript. Questo comportamento consente a qualsiasi funzione di avere accesso all'ambito in cui è stata creata, a tempo indeterminato.Per applicarlo praticamente, invoco immediatamente una funzione che restituisce un'altra funzione e poiché la funzione che sto restituendo ha accesso alla variabile di conteggio interna (a causa del comportamento di chiusura spiegato sopra), ciò si traduce in un ambito privato per l'utilizzo da parte del risultante funzione ... Non così semplice? Diluiamolo ...

Una semplice chiusura a una riga

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

Tutte le variabili esterne alla funzione restituita sono disponibili per la funzione restituita, ma non sono direttamente disponibili per l'oggetto funzione restituito ...

func();  // Alerts "val"
func.a;  // Undefined

Prendilo? Quindi, nel nostro esempio principale, la variabile count è contenuta nella chiusura e sempre disponibile per il gestore eventi, quindi mantiene il suo stato da un clic all'altro.

Inoltre, questo stato variabile privato è completamente accessibile, sia per le letture che per l'assegnazione alle sue variabili con ambito privato.

Ecco qua; ora stai incapsulando completamente questo comportamento.

Post completo sul blog (comprese le considerazioni su jQuery)


11
Non sono d'accordo con la tua definizione di cosa sia una chiusura. Non c'è motivo per cui deve essere auto-invocante. È anche un po 'semplicistico (e inesatto) dire che deve essere "restituito" (molte discussioni su questo nei commenti della risposta principale a questa domanda)
James Montagne,

40
@James anche se non sei d'accordo, il suo esempio (e l'intero post) è uno dei migliori che abbia mai visto. Mentre la domanda non è vecchia e risolta per me, merita totalmente un +1.
e-soddisfa il

84
"Devo sapere quante volte è stato cliccato un pulsante e fare qualcosa ogni terzo clic ..." Questo ha attirato la mia attenzione. Un caso d'uso e la soluzione che mostra come una chiusura non sia una cosa così misteriosa e che molti di noi li hanno scritti ma non conoscevano esattamente il nome ufficiale.
Chris22,

Bel esempio perché mostra che "count" nel secondo esempio mantiene il valore di "count" e non si reimposta su 0 ogni volta che si fa clic sull'elemento. Molto informativo!
Adam,

+1 per il comportamento di chiusura . Possiamo limitare il comportamento di chiusura a funzioni in javascript o questo concetto può essere applicato anche ad altre strutture del linguaggio?
Dziamid,

493

Le chiusure sono difficili da spiegare perché vengono utilizzate per far funzionare alcuni comportamenti che tutti si aspettano intuitivamente di lavorare comunque. Trovo il modo migliore per spiegarli (e il modo in cui ho imparato cosa fanno) è immaginare la situazione senza di loro:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

Cosa accadrebbe qui se JavaScript non conoscesse le chiusure? Basta sostituire la chiamata nell'ultima riga con il suo metodo body (che è fondamentalmente ciò che fanno le chiamate di funzione) e si ottiene:

console.log(x + 3);

Ora, dov'è la definizione di x? Non lo abbiamo definito nell'ambito attuale. L'unica soluzione è quella di lasciare che plus5 portare la sua portata (o meglio, la portata del suo genitore) in giro. In questo modo, xè ben definito ed è associato al valore 5.


11
Questo è esattamente il tipo di esempio che induce in errore molte persone a pensare che siano i valori utilizzati nella funzione restituita, non la variabile modificabile stessa. Se fosse cambiato in "return x + = y", o meglio ancora sia quello che un'altra funzione "x * = y", allora sarebbe chiaro che non viene copiato nulla. Per le persone abituate a impilare i frame, immagina invece di utilizzare i frame heap, che possono continuare a esistere anche dopo il ritorno della funzione.
Matt,

14
@Matt Non sono d'accordo. Un esempio non dovrebbe documentare esaurientemente tutte le proprietà. È pensato per essere riduttivo e illustrare la caratteristica saliente di un concetto. Il PO ha chiesto una spiegazione semplice ("per un bambino di sei anni"). Prendi la risposta accettata: non riesce assolutamente a fornire una spiegazione concisa, proprio perché cerca di essere esaustiva. (Sono d'accordo con te sul fatto che sia un'importante proprietà di JavaScript che l'associazione sia per riferimento piuttosto che per valore ... ma ancora una volta, una spiegazione riuscita è quella che si riduce al minimo indispensabile.)
Konrad Rudolph,

@KonradRudolph Mi piace lo stile e la brevità del tuo esempio. Consiglio semplicemente di cambiarlo leggermente in modo che la parte finale, "L'unica soluzione sia ...", diventi vera. Attualmente non v'è in realtà un'altra, soluzione più semplice per lo scenario, che non non corrisponde a continuazioni javascript, e non corrispondono a un malinteso comune su ciò che continuazioni sono. Pertanto l'esempio nella sua forma attuale è pericoloso. Questo non ha a che fare con l'elenco esaustivo delle proprietà, ha a che fare con la comprensione di ciò che x è nella funzione restituita, che è dopo tutto il punto principale.
Matt,

@Matt Hmm, non sono sicuro di averti compreso appieno, ma comincio a capire che potresti avere un punto valido. Dato che i commenti sono troppo brevi, potresti forse spiegare cosa intendi in un gist / pastie o in una chat room? Grazie.
Konrad Rudolph,

2
@KonradRudolph Penso di non essere stato chiaro sullo scopo di x + = y. Lo scopo era solo quello di mostrare che le chiamate ripetute alla funzione restituita continuano a usare la stessa variabile x (al contrario dello stesso valore , che la gente immagina sia "inserita" quando viene creata la funzione). Questo è come i primi due avvisi nel violino. Lo scopo di una funzione aggiuntiva x * = y sarebbe quello di mostrare che più funzioni restituite condividono tutte la stessa x.
Matt,

379

TLDR

Una chiusura è un collegamento tra una funzione e il suo ambiente lessicale esterno (cioè scritto), in modo tale che gli identificatori (variabili, parametri, dichiarazioni di funzione ecc.) Definiti all'interno di quell'ambiente siano visibili dall'interno della funzione, indipendentemente da quando o da dove viene invocata la funzione.

Dettagli

Nella terminologia della specifica ECMAScript, si può dire che una chiusura sia implementata dal [[Environment]]riferimento di ogni oggetto-funzione, che punta all'ambiente lessicale in cui è definita la funzione.

Quando una funzione viene invocata tramite il [[Call]]metodo interno , il [[Environment]]riferimento sull'oggetto funzione viene copiato nel riferimento ambiente esterno del record ambiente del contesto di esecuzione appena creato (stack frame).

Nel seguente esempio, la funzione si fchiude sull'ambiente lessicale del contesto di esecuzione globale:

function f() {}

Nel seguente esempio, la funzione si hchiude sull'ambiente lessicale della funzione g, che a sua volta si chiude sull'ambiente lessicale del contesto di esecuzione globale.

function g() {
    function h() {}
}

Se una funzione interna viene restituita da un esterno, l'ambiente lessicale esterno persisterà dopo il ritorno della funzione esterna. Questo perché l'ambiente lessicale esterno deve essere disponibile se la funzione interna viene infine invocata.

Nel seguente esempio, la funzione si jchiude sull'ambiente lessicale della funzione i, il che significa che la variabile xè visibile dall'interno della funzione j, molto tempo dopo che la funzione iha completato l'esecuzione:

function i() {
    var x = 'mochacchino'
    return function j() {
        console.log('Printing the value of x, from within function j: ', x)
    }
} 

const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500ms

In chiusura, le variabili nell'ambiente lessicale esterna stessi sono disponibili, non copie.

function l() {
  var y = 'vanilla';

  return {
    setY: function(value) {
      y = value;
    },
    logY: function(value) {
      console.log('The value of y is: ', y);
    }
  }
}

const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolate

La catena di ambienti lessicali, collegata tra contesti di esecuzione tramite riferimenti di ambiente esterno, forma una catena di ambito e definisce gli identificatori visibili da una data funzione.

Nel tentativo di migliorare la chiarezza e l'accuratezza, questa risposta è stata sostanzialmente modificata rispetto all'originale.


56
Wow, non ho mai saputo che potresti usare sostituzioni di stringhe in console.logquel modo. Se qualcun altro è interessato, ce ne sono altri: developer.mozilla.org/en-US/docs/DOM/…
Flash

7
Anche le variabili che si trovano nell'elenco dei parametri della funzione fanno parte della chiusura (ad esempio non solo a var).
Thomas Eding,

Le chiusure suonano più come oggetti e classi ecc. Non so perché molte persone non paragonano queste due cose - sarebbe più facile per noi principianti imparare!
almaruf,

376

OK, fan delle chiusure di 6 anni. Vuoi ascoltare l'esempio più semplice di chiusura?

Immaginiamo la prossima situazione: un guidatore è seduto in una macchina. Quell'auto è dentro un aereo. L'aereo è in aeroporto. La capacità del conducente di accedere a cose al di fuori della sua auto, ma all'interno dell'aereo, anche se quell'aereo lascia un aeroporto, è una chiusura. Questo è tutto. Quando compirai 27 anni, guarda la spiegazione più dettagliata o l'esempio di seguito.

Ecco come posso convertire la mia storia dell'aereo nel codice.

var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");


26
Ben giocato e risponde al poster originale. Penso che questa sia la risposta migliore. Stavo per usare i bagagli in un modo simile: immagina di andare a casa della nonna e impacchettare la custodia del tuo Nintendo DS con le carte da gioco all'interno della custodia, ma poi mettere la custodia nello zaino e mettere anche le carte da gioco nelle tasche dello zaino, e POI hai messo il tutto in una grande valigia con più carte da gioco nelle tasche della valigia. Quando arrivi a casa della nonna, puoi giocare a qualsiasi gioco sul tuo DS fintanto che tutti i casi esterni sono aperti. o qualcosa in tal senso.
slartibartfast,

366

Questo è un tentativo di chiarire diversi (possibili) equivoci sulle chiusure che compaiono in alcune delle altre risposte.

  • Una chiusura non viene creata solo quando si restituisce una funzione interna. In effetti, la funzione che racchiude non ha bisogno di tornare per poter creare la sua chiusura. È possibile invece assegnare la funzione interna a una variabile in un ambito esterno o passarla come argomento a un'altra funzione in cui potrebbe essere chiamata immediatamente o in qualsiasi momento successivo. Pertanto, la chiusura della funzione di chiusura viene probabilmente creata non appena viene chiamata la funzione di chiusura poiché qualsiasi funzione interna ha accesso a quella chiusura ogni volta che viene chiamata la funzione interna, prima o dopo il ritorno della funzione di chiusura.
  • Una chiusura non fa riferimento a una copia dei vecchi valori delle variabili nel suo ambito. Le variabili stesse fanno parte della chiusura, quindi il valore visualizzato quando si accede a una di quelle variabili è l'ultimo valore al momento dell'accesso. Questo è il motivo per cui le funzioni interne create all'interno dei loop possono essere complicate, poiché ognuna ha accesso alle stesse variabili esterne anziché prendere una copia delle variabili nel momento in cui la funzione viene creata o chiamata.
  • Le "variabili" in una chiusura includono qualsiasi funzione denominata dichiarata all'interno della funzione. Includono anche argomenti della funzione. Una chiusura ha anche accesso alle sue variabili di chiusura, fino all'ambito globale.
  • Le chiusure utilizzano la memoria, ma non causano perdite di memoria poiché JavaScript da solo pulisce le proprie strutture circolari a cui non si fa riferimento. Le perdite di memoria di Internet Explorer che coinvolgono le chiusure vengono create quando non riesce a disconnettere i valori degli attributi DOM che fanno riferimento alle chiusure, mantenendo così i riferimenti a possibili strutture circolari.

15
James, ho detto che la chiusura è "probabilmente" creata al momento della chiamata della funzione che racchiude perché è plausibile che un'implementazione possa ritardare la creazione di una chiusura fino a qualche tempo dopo, quando decide che una chiusura è assolutamente necessaria. Se non è stata definita alcuna funzione interna nella funzione di chiusura, non sarà necessaria alcuna chiusura. Quindi forse potrebbe attendere fino alla creazione della prima funzione interna per creare una chiusura fuori dal contesto di chiamata della funzione che lo racchiude.
dlaliberte,

9
@ Barbabietola-Barbabietola Supponiamo di avere una funzione interna che viene passata a un'altra funzione in cui viene utilizzata prima che ritorni la funzione esterna e supponiamo di restituire anche la stessa funzione interna dalla funzione esterna. È identicamente la stessa funzione in entrambi i casi, ma stai dicendo che prima che la funzione esterna ritorni, la funzione interna è "legata" allo stack di chiamate, mentre dopo che ritorna, la funzione interna è improvvisamente vincolata a una chiusura. Si comporta in modo identico in entrambi i casi; la semantica è identica, quindi non stai solo parlando dei dettagli di implementazione?
dlaliberte,

7
@ Barbabietola rossa, grazie per il tuo feedback, e sono felice di averti fatto pensare. Non vedo ancora alcuna differenza semantica tra il contesto live della funzione esterna e quello stesso contesto quando diventa una chiusura quando la funzione ritorna (se capisco la tua definizione). Alla funzione interna non importa. La garbage collection non si preoccupa poiché la funzione interna mantiene un riferimento al contesto / chiusura in entrambi i modi e il chiamante della funzione esterna rilascia semplicemente il riferimento al contesto della chiamata. Ma è confuso per le persone, e forse meglio chiamarlo semplicemente un contesto di chiamata.
dlaliberte,

9
Quell'articolo è difficile da leggere, ma penso che supporti effettivamente quello che sto dicendo. Dice: "Una chiusura si forma restituendo un oggetto funzione [...] o assegnando direttamente un riferimento a tale oggetto funzione, ad esempio, a una variabile globale". Non intendo che GC sia irrilevante. Piuttosto, a causa di GC, e poiché la funzione interna è collegata al contesto di chiamata della funzione esterna (o [[ambito]] come dice l'articolo), allora non importa se la chiamata di funzione esterna ritorni perché quella associazione con quella interna la funzione è la cosa importante.
dlaliberte,

3
Bella risposta! Una cosa da aggiungere è che tutte le funzioni si chiudono sull'intero contenuto dell'ambito di esecuzione in cui sono definite. Non importa se si riferiscono ad alcune o nessuna delle variabili dall'ambito genitore: un riferimento all'ambiente lessicale dell'ambito genitore viene archiviato come [[Scope]] incondizionatamente. Questo può essere visto dalla sezione sulla creazione della funzione nelle specifiche ECMA.
Asad Saeeduddin,

236

Qualche tempo fa ho scritto un post sul blog spiegando le chiusure. Ecco cosa ho detto sulle chiusure in termini di perché ne vorresti una.

Le chiusure sono un modo per consentire a una funzione di avere variabili private persistenti, ovvero variabili che solo una funzione conosce, in cui può tenere traccia delle informazioni delle volte precedenti in cui è stata eseguita.

In tal senso, lasciano che una funzione si comporti un po 'come un oggetto con attributi privati.

Post completo:

Quindi quali sono questi oggetti di chiusura?


Quindi il principale vantaggio delle chiusure potrebbe essere enfatizzato con questo esempio? Dire che ho una funzione emailError (sendToAddress, errorString) Potrei quindi dire devError = emailError("devinrhode2@googmail.com", errorString)e quindi avere la mia versione personalizzata di una funzione emailError condivisa?
Devin G Rhode,

Questa spiegazione e l'esempio perfetto associato nel collegamento a (elementi di chiusura) è il modo migliore per comprendere le chiusure e dovrebbe essere proprio al top!
HopeKing

215

Le chiusure sono semplici:

Il seguente semplice esempio copre tutti i punti principali delle chiusure JavaScript. *  

Ecco una fabbrica che produce calcolatori che possono aggiungere e moltiplicare:

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

Il punto chiave: ogni chiamata a make_calculatorcrea una nuova variabile locale n, che continua a essere utilizzabile da quella calcolatrice adde multiplyfunziona a lungo dopo i make_calculatorritorni.

Se hai familiarità con i frame stack, questi calcolatori sembrano strani: come possono continuare ad accedere ndopo i make_calculatorritorni? La risposta è immaginare che JavaScript non usi "frame stack", ma invece utilizzi "frame heap", che possono persistere dopo la chiamata della funzione che li ha resi restituiti.

Le funzioni interne come adde multiply, che accedono alle variabili dichiarate in una funzione esterna ** , sono chiamate chiusure .

Questo è praticamente tutto ciò che c'è da chiudere.



* Ad esempio, copre tutti i punti dell'articolo "Chiusure per manichini" riportato in un'altra risposta , ad eccezione dell'esempio 6, che mostra semplicemente che le variabili possono essere utilizzate prima che vengano dichiarate, un fatto interessante da sapere ma completamente estraneo alle chiusure. Copre anche tutti i punti di questa risposta della risposta accettata , ad eccezione dei punti (1) che le funzioni copiano i loro argomenti in variabili locali (gli argomenti della funzione denominata) e (2) che la copia dei numeri crea un nuovo numero, ma la copia di un riferimento a un oggetto ti dà un altro riferimento allo stesso oggetto. Anche questi sono buoni da sapere ma di nuovo completamente estranei alle chiusure. È anche molto simile all'esempio in questa risposta ma un po 'più breve e meno astratto. Non copre il punto di questo commento o corrente, ovvero JavaScript rende difficile collegare ilvalore di una variabile loop nella funzione interna: il passaggio "plug in" può essere eseguito solo con una funzione helper che racchiude la funzione interna e viene invocata su ogni iterazione di loop. (A rigor di termini, la funzione interna accede alla copia della variabile helper della variabile, piuttosto che avere qualcosa collegato.) Ancora una volta, molto utile quando si creano chiusure, ma non fa parte di ciò che è una chiusura o come funziona. Vi è ulteriore confusione dovuta alle chiusure che funzionano in modo diverso nei linguaggi funzionali come ML, in cui le variabili sono legate ai valori piuttosto che allo spazio di archiviazione, fornendo un flusso costante di persone che comprendono le chiusure in un modo (vale a dire il "plug-in") che è semplicemente errato per JavaScript, dove le variabili sono sempre legate allo spazio di archiviazione e mai ai valori.

** Qualsiasi funzione esterna, se molte sono nidificate, o anche nel contesto globale, come indica chiaramente questa risposta .


Cosa succederebbe se tu chiamassi: second_calculator = first_calculator (); invece di second_calculator = make_calculator (); ? Dovrebbe essere lo stesso, giusto?
Ronen Festinger,

4
@Ronen: poiché first_calculatorè un oggetto (non una funzione) non dovresti usare le parentesi second_calculator = first_calculator;, poiché è un compito, non una chiamata di funzione. Per rispondere alla tua domanda, ci sarebbe solo una chiamata a make_calculator, quindi verrebbe effettuata una sola calcolatrice, e le variabili first_calculator e second_calculator si riferirebbero entrambe allo stesso calcolatore, quindi le risposte sarebbero 3, 403, 4433, 44330.
Matt,

204

Come lo spiegherei a un bambino di sei anni:

Sai come gli adulti possono possedere una casa e la chiamano casa? Quando una mamma ha un figlio, il bambino non possiede davvero nulla, giusto? Ma i suoi genitori possiedono una casa, quindi ogni volta che qualcuno chiede al bambino "Dov'è la tua casa?", Lui / lei può rispondere "quella casa!", E indicare la casa dei suoi genitori. Una "chiusura" è la capacità del bambino di poter sempre (anche se all'estero) poter dire che ha una casa, anche se in realtà è il genitore che possiede la casa.


200

Puoi spiegare le chiusure a un bambino di 5 anni? *

Penso ancora che la spiegazione di Google funzioni molto bene ed è concisa:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

Prova che questo esempio crea una chiusura anche se la funzione interna non ritorna

* Domanda # AC


11
Il codice è "corretto", come esempio di chiusura, anche se non affronta la parte del commento sull'uso della chiusura dopo la restituzione di outerFunction. Quindi non è un grande esempio. Esistono molti altri modi in cui è possibile utilizzare una chiusura che non comporta la restituzione della funzione interna. ad esempio innerFunction potrebbe essere passato a un'altra funzione in cui viene chiamato immediatamente o memorizzato e chiamato qualche tempo dopo, e in tutti i casi, ha accesso al contesto outerFunction che è stato creato quando è stato chiamato.
dlaliberte,

6
@syockit No, Moss ha torto. Viene creata una chiusura indipendentemente dal fatto che la funzione sfugga mai all'ambito in cui è definita e un riferimento creato incondizionatamente all'ambiente lessicale del genitore rende tutte le variabili nell'ambito genitore disponibili per tutte le funzioni, indipendentemente dal fatto che siano invocate all'esterno o all'interno l'ambito in cui sono stati creati.
Asad Saeeduddin,

176

Tendo a imparare meglio dai confronti BUONO / CATTIVO. Mi piace vedere il codice funzionante seguito dal codice non funzionante che qualcuno potrebbe incontrare. Ho messo insieme un jsFiddle che fa un confronto e cerca di ridurre le differenze con le spiegazioni più semplici che ho potuto inventare .

Chiusure eseguite correttamente:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • Nel codice sopra createClosure(n)è invocato in ogni iterazione del ciclo. Si noti che ho chiamato la variabile nper evidenziare che si tratta di una nuova variabile creata in un nuovo ambito di funzione e non è la stessa variabile indexassociata all'ambito esterno.

  • Ciò crea un nuovo ambito ed nè vincolato a tale ambito; questo significa che abbiamo 10 ambiti separati, uno per ogni iterazione.

  • createClosure(n) restituisce una funzione che restituisce n all'interno di tale ambito.

  • All'interno di ogni ambito nè associato a qualunque valore avesse quando è createClosure(n)stato invocato, quindi la funzione nidificata che viene restituita restituirà sempre il valore din quella che aveva quando è createClosure(n)stata invocata.

Chiusure sbagliate:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • Nel codice sopra il loop è stato spostato all'interno della createClosureArray()funzione e la funzione ora restituisce solo l'array completo, che a prima vista sembra più intuitivo.

  • Ciò che potrebbe non essere ovvio è che da allora createClosureArray() viene invocato solo una volta viene creato un solo ambito per questa funzione anziché uno per ogni iterazione del ciclo.

  • All'interno di questa funzione indexviene definita una variabile denominata . Il ciclo viene eseguito e aggiunge funzioni all'array che ritorna index. Si noti che indexè definito all'interno dicreateClosureArray funzione che viene invocata solo una volta.

  • Poiché nella funzione era presente un solo ambito createClosureArray(), indexè associato solo a un valore all'interno di tale ambito. In altre parole, ogni volta che il ciclo cambia il valore di index, lo cambia per tutto ciò che lo fa riferimento in quell'ambito.

  • Tutte le funzioni aggiunte all'array restituiscono la indexvariabile SAME dall'ambito padre in cui è stata definita anziché 10 diverse da 10 ambiti diversi come il primo esempio. Il risultato finale è che tutte e 10 le funzioni restituiscono la stessa variabile dallo stesso ambito.

  • Dopo che il ciclo è terminato ed indexè stato modificato, il valore finale era 10, quindi ogni funzione aggiunta all'array restituisce il valore della singola indexvariabile che è ora impostata su 10.

Risultato

CHIUSURE FATTE DESTRA
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

CHIUSURE FATTE ERRATE
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10


1
Bella aggiunta, grazie. Giusto per rendere più chiaro uno può immaginare come l'array "cattivo" viene creato nel ciclo "cattivo" con ogni iterazione: 1a iterazione: [function () {return 'n =' + 0;}] 2a iterazione: [( function () {return 'n =' + 1;}), (function () {return 'n =' + 1;})] 3a iterazione: [(function () {return 'n =' + 2;}) , (function () {return 'n =' + 2;}), (function () {return 'n =' + 2;})] ecc. Quindi, ogni volta che il valore dell'indice cambia, si riflette in tutte le funzioni già aggiunto all'array.
Alex Alexeev,

3
L'uso di letper varrisolve la differenza.
Rupam Datta,

Non è qui "La chiusura fatta bene" è un esempio di "chiusura all'interno della chiusura"?
TechnicalSmile

Voglio dire, ogni funzione è tecnicamente una chiusura, ma la parte importante è che la funzione definisce una nuova variabile all'interno. La funzione che ottiene restituisce solo i riferimenti ncreati in una nuova chiusura. Restituiamo semplicemente una funzione in modo da poterla memorizzare nell'array e richiamarla in un secondo momento.
Chev,

Se si desidera memorizzare solo il risultato nella matrice nella prima iterazione, allora si potrebbe inline in questo modo: arr[index] = (function (n) { return 'n = ' + n; })(index);. Ma poi stai memorizzando la stringa risultante nell'array piuttosto che una funzione da invocare che sconfigge il punto del mio esempio.
Chev,

164

Wikipedia sulle chiusure :

In informatica, una chiusura è una funzione insieme a un ambiente di riferimento per i nomi non locali (variabili libere) di quella funzione.

Tecnicamente, in JavaScript , ogni funzione è una chiusura . Ha sempre accesso alle variabili definite nell'ambito circostante.

Poiché la costruzione che definisce l'ambito in JavaScript è una funzione , non un blocco di codice come in molte altre lingue, ciò che di solito intendiamo per chiusura in JavaScript è una funzione che lavora con variabili non locali definite nella funzione circostante già eseguita .

Le chiusure vengono spesso utilizzate per creare funzioni con alcuni dati privati ​​nascosti (ma non è sempre il caso).

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

L'esempio sopra sta usando una funzione anonima, che è stata eseguita una volta. Ma non deve essere. Può essere nominato (ad es. mkdb) Ed eseguito in un secondo momento, generando una funzione di database ogni volta che viene invocato. Ogni funzione generata avrà il suo oggetto database nascosto. Un altro esempio di utilizzo delle chiusure è quando non restituiamo una funzione, ma un oggetto contenente più funzioni per scopi diversi, ognuna di quelle funzioni che hanno accesso agli stessi dati.


2
Questa è la migliore spiegazione per le chiusure JavaScript. Dovrebbe essere la risposta scelta. Il resto è abbastanza divertente ma questo è effettivamente utile in modo pratico per i programmatori JavaScript del mondo reale.
geoidesic

136

Ho messo insieme un tutorial JavaScript interattivo per spiegare come funzionano le chiusure. Che cos'è una chiusura?

Ecco uno degli esempi:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

128

I bambini ricorderanno sempre i segreti che hanno condiviso con i genitori, anche dopo che i genitori se ne saranno andati. Ecco cosa sono le chiusure per le funzioni.

I segreti per le funzioni JavaScript sono le variabili private

var parent = function() {
 var name = "Mary"; // secret
}

Ogni volta che lo chiami, viene creata la variabile locale "nome" e viene dato il nome "Maria". E ogni volta che la funzione esce la variabile viene persa e il nome viene dimenticato.

Come puoi immaginare, poiché le variabili vengono ricreate ogni volta che viene chiamata la funzione e nessun altro le conoscerà, deve esserci un posto segreto in cui sono memorizzate. Potrebbe essere chiamato Chamber of Secrets o stack o scope locale ma non importa. Sappiamo che sono lì, da qualche parte, nascosti nella memoria.

Ma in JavaScript c'è questa cosa molto speciale che funzioni create all'interno di altre funzioni, possono anche conoscere le variabili locali dei loro genitori e mantenerle per tutto il tempo in cui vivono.

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

Quindi, fintanto che siamo nella funzione genitore, può creare una o più funzioni figlio che condividono le variabili segrete dal luogo segreto.

Ma la cosa triste è che se il bambino è anche una variabile privata della sua funzione genitore, morirebbe anche quando il genitore finisce, e i segreti morirebbero con loro.

Quindi per vivere, il bambino deve partire prima che sia troppo tardi

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

E ora, anche se Mary "non corre più", il ricordo di lei non è perduto e suo figlio ricorderà sempre il suo nome e altri segreti che hanno condiviso durante il loro tempo insieme.

Quindi, se chiami la bambina "Alice", lei risponderà

child("Alice") => "My name is Alice, child of Mary"

Questo è tutto ciò che c'è da dire.


15
Questa è la spiegazione che aveva più senso per me perché non presuppone una conoscenza significativa dei termini tecnici. La spiegazione più votata qui presuppone che la persona che non capisce le chiusure abbia una comprensione completa e completa di termini come "ambito lessicale" e "contesto di esecuzione" - mentre posso capirli concettualmente, non penso di essere come a mio agio con i dettagli di loro come dovrei essere, e la spiegazione senza gergo in ciò è ciò che ha reso le chiusure finalmente cliccate per me, grazie. Come bonus, penso che spieghi anche quale ambito è molto conciso.
Emma W,

103

Non capisco perché le risposte siano così complesse qui.

Ecco una chiusura:

var a = 42;

function b() { return a; }

Sì. Probabilmente lo usi molte volte al giorno.


Non vi è motivo di ritenere che le chiusure siano un trucco di progettazione complesso per affrontare problemi specifici. No, le chiusure riguardano solo l'utilizzo di una variabile che proviene da un ambito più elevato dal punto di vista di dove la funzione è stata dichiarata (non eseguita) .

Ora, ciò che ti consente di fare può essere più spettacolare, vedi altre risposte.


5
Questa risposta non sembra probabile che aiuti a confondere le persone. Un equivalente approssimativo in un linguaggio di programmazione tradizionale potrebbe essere quello di creare b () come metodo su un oggetto che ha anche una costante o proprietà privata a. A mio avviso, la sorpresa è che l'oggetto scope JS fornisce effettivamente auna proprietà anziché una costante. E noterai quel comportamento importante solo se lo modifichi, come inreturn a++;
Jon Coombs

1
Esattamente quello che ha detto Jon. Prima di chiudere finalmente le chiusure ho avuto difficoltà a trovare esempi pratici. Sì, Floribon ha creato una chiusura, ma per non istruirlo questo non mi avrebbe insegnato assolutamente nulla.
Chev,

3
Questo non definisce cosa sia una chiusura - è semplicemente un esempio che ne utilizza una. E non affronta la sfumatura di ciò che accade quando finisce l'ambito; Non penso che qualcuno avesse una domanda sullo scoping lessicale quando tutti gli ambiti sono ancora in giro, e specialmente nel caso di una variabile globale.
Gerard ONeill,

92

Esempio per il primo punto di dlaliberte:

Una chiusura non viene creata solo quando si restituisce una funzione interna. In effetti, la funzione che racchiude non ha bisogno di tornare affatto. È possibile invece assegnare la funzione interna a una variabile in un ambito esterno o passarla come argomento a un'altra funzione in cui potrebbe essere utilizzata immediatamente. Pertanto, la chiusura della funzione di chiusura probabilmente esiste già nel momento in cui è stata chiamata la funzione di chiusura poiché qualsiasi funzione interna ha accesso ad essa non appena viene chiamata.

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);

Piccoli chiarimenti su una possibile ambiguità. Quando ho detto "In effetti, la funzione che racchiude non ha bisogno di tornare affatto". Non intendevo "non restituire alcun valore" ma "ancora attivo". Quindi l'esempio non mostra quell'aspetto, sebbene mostri un altro modo in cui la funzione interna può essere passata all'ambito esterno. Il punto principale che stavo cercando di fare riguarda il momento della creazione della chiusura (per la funzione di chiusura), poiché alcune persone sembrano pensare che ciò accada quando ritorna la funzione di chiusura. È richiesto un esempio diverso per mostrare che la chiusura viene creata quando viene chiamata una funzione .
dlaliberte,

89

Una chiusura è dove una funzione interna ha accesso alle variabili nella sua funzione esterna. Questa è probabilmente la spiegazione di una riga più semplice che puoi ottenere per le chiusure.


35
Questa è solo metà della spiegazione. La cosa importante da notare sulle chiusure è che se si fa ancora riferimento alla funzione interna dopo che la funzione esterna è uscita, i vecchi valori della funzione esterna sono ancora disponibili per quella interna.
pcorcoran,

22
In realtà, non sono i vecchi valori della funzione esterna a essere disponibili per la funzione interna, ma le vecchie variabili , che potrebbero avere nuovi valori se una funzione fosse in grado di cambiarle.
dlaliberte,

86

So che ci sono già molte soluzioni, ma credo che questo piccolo e semplice script possa essere utile per dimostrare il concetto:

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

82

Stai dormendo e inviti Dan. Di 'a Dan di portare un controller XBox.

Dan invita Paul. Dan chiede a Paul di portare un controller. Quanti controller sono stati portati alla festa?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

80

L'autore di Closures ha spiegato abbastanza bene le chiusure, spiegando il motivo per cui ne abbiamo bisogno e spiegando anche LexicalEnvironment che è necessario per comprendere le chiusure.
Ecco il riassunto:

Cosa succede se si accede a una variabile, ma non è locale? Come qui:

Inserisci qui la descrizione dell'immagine

In questo caso, l'interprete trova la variabile LexicalEnvironmentnell'oggetto esterno .

Il processo prevede due passaggi:

  1. Innanzitutto, quando viene creata una funzione f, non viene creata in uno spazio vuoto. Esiste un oggetto LexicalEnvironment corrente. Nel caso sopra, è window (a non è definito al momento della creazione della funzione).

Inserisci qui la descrizione dell'immagine

Quando viene creata una funzione, ottiene una proprietà nascosta, denominata [[Scope]], che fa riferimento al LexicalEnvironment corrente.

Inserisci qui la descrizione dell'immagine

Se viene letta una variabile, ma non può essere trovata da nessuna parte, viene generato un errore.

Funzioni nidificate

Le funzioni possono essere nidificate l'una all'interno dell'altra, formando una catena di ambienti lessicali che può anche essere definita una catena di ambito.

Inserisci qui la descrizione dell'immagine

Quindi, la funzione g ha accesso a g, a e f.

chiusure

Una funzione nidificata può continuare a vivere al termine della funzione esterna:

Inserisci qui la descrizione dell'immagine

Marking LexicalEnvironments:

Inserisci qui la descrizione dell'immagine

Come vediamo, this.sayè una proprietà nell'oggetto utente, quindi continua a vivere dopo il completamento dell'utente.

E se ricordi, quando this.sayviene creato, esso (come ogni funzione) ottiene un riferimento interno this.say.[[Scope]]all'attuale LexicalEnvironment. Pertanto, l'ambiente lessicale dell'attuale esecuzione dell'utente rimane in memoria. Tutte le variabili dell'Utente sono anche le sue proprietà, quindi sono anche mantenute con cura, non spazzate via come al solito.

Il punto è garantire che se la funzione interna vuole accedere a una variabile esterna in futuro, è in grado di farlo.

Riassumere:

  1. La funzione interna mantiene un riferimento all'ambiente lessicale esterno.
  2. La funzione interna può accedere alle variabili da essa in qualsiasi momento anche se la funzione esterna è terminata.
  3. Il browser mantiene in memoria LexicalEnvironment e tutte le sue proprietà (variabili) fino a quando non vi è una funzione interna che lo fa riferimento.

Questa si chiama chiusura.


78

Le funzioni JavaScript possono accedere a:

  1. argomenti
  2. Locali (ovvero, le loro variabili locali e le funzioni locali)
  3. Ambiente, che include:
    • globali, incluso il DOM
    • qualsiasi cosa nelle funzioni esterne

Se una funzione accede al suo ambiente, allora la funzione è una chiusura.

Si noti che le funzioni esterne non sono richieste, sebbene offrano vantaggi di cui non discuto qui. Accedendo ai dati nel suo ambiente, una chiusura mantiene tali dati in vita. Nella sottocassa delle funzioni esterne / interne, una funzione esterna può creare dati locali ed eventualmente uscire, e tuttavia, se una o più funzioni interne sopravvivono dopo l'uscita della funzione esterna, le funzioni interne mantengono i dati locali della funzione esterna vivo.

Esempio di chiusura che utilizza l'ambiente globale:

Immagina che gli eventi del pulsante di voto in alto e di voto in eccesso di Stack Overflow siano implementati come chiusure, votoUp_click e votoDown_click, che hanno accesso a variabili esterne isVotedUp e isVotedDown, che sono definite a livello globale. (Per semplicità, mi riferisco ai pulsanti di voto delle domande di StackOverflow, non alla matrice dei pulsanti di voto delle risposte.)

Quando l'utente fa clic sul pulsante VoteUp, la funzione voteUp_click verifica se isVotedDown == true per determinare se votare in alto o semplicemente annullare un voto negativo. La funzione voteUp_click è una chiusura perché sta accedendo al suo ambiente.

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

Tutte e quattro queste funzioni sono chiusure poiché accedono tutte al loro ambiente.


59

Come padre di un bambino di 6 anni, che attualmente insegna ai bambini piccoli (e un novizio relativo alla programmazione senza educazione formale, quindi saranno necessarie correzioni), penso che la lezione si atterrebbe meglio attraverso il gioco pratico. Se il bambino di 6 anni è pronto a capire cos'è una chiusura, allora è abbastanza grande da provare da solo. Suggerirei di incollare il codice in jsfiddle.net, spiegandone un po 'e lasciandoli soli per creare una canzone unica. Il testo esplicativo che segue è probabilmente più appropriato per un bambino di 10 anni.

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

ISTRUZIONI

DATI: i dati sono una raccolta di fatti. Possono essere numeri, parole, misure, osservazioni o anche solo descrizioni di cose. Non puoi toccarlo, annusarlo o assaggiarlo. Puoi scriverlo, parlarlo e ascoltarlo. Puoi usarlo per creare odore e gusto tatto usando un computer. Può essere reso utile da un computer usando il codice.

CODICE: tutto lo scritto sopra è chiamato codice . È scritto in JavaScript.

JAVASCRIPT: JavaScript è una lingua. Come l'inglese o il francese o il cinese sono le lingue. Esistono molte lingue che sono comprese dai computer e da altri processori elettronici. Affinché JavaScript sia compreso da un computer, è necessario un interprete. Immagina se un insegnante che parla solo russo viene a insegnare a lezione a scuola. Quando l'insegnante dice "все садятся", la classe non capisce. Ma per fortuna hai un allievo russo nella tua classe che dice a tutti che questo significa "tutti siediti" - quindi lo fate tutti. La classe è come un computer e l'allievo russo è l'interprete. Per JavaScript l'interprete più comune si chiama browser.

BROWSER: quando ti connetti a Internet su un computer, tablet o telefono per visitare un sito Web, usi un browser. Esempi che potresti conoscere sono Internet Explorer, Chrome, Firefox e Safari. Il browser può capire JavaScript e dire al computer cosa deve fare. Le istruzioni JavaScript sono chiamate funzioni.

FUNZIONE: una funzione in JavaScript è come una fabbrica. Potrebbe essere una piccola fabbrica con solo una macchina all'interno. Oppure potrebbe contenere molte altre piccole fabbriche, ognuna con molte macchine che svolgono lavori diversi. In una vera fabbrica di abbigliamento potresti avere risme di stoffa e bobine di filo in entrata e magliette e jeans che escono. La nostra fabbrica JavaScript elabora solo i dati, non può cucire, praticare un foro o fondere il metallo. Nella nostra fabbrica di JavaScript i dati entrano ed escono i dati.

Tutta questa roba di dati sembra un po 'noiosa, ma è davvero molto bella; potremmo avere una funzione che dice a un robot cosa preparare per cena. Diciamo che invito te e il tuo amico a casa mia. Ti piacciono le cosce di pollo, mi piacciono le salsicce, il tuo amico vuole sempre quello che vuoi e il mio amico non mangia carne.

Non ho tempo per fare shopping, quindi la funzione deve sapere cosa abbiamo in frigo per prendere decisioni. Ogni ingrediente ha un tempo di cottura diverso e vogliamo che tutto sia servito caldo dal robot allo stesso tempo. Dobbiamo fornire alla funzione i dati su ciò che ci piace, la funzione potrebbe "parlare" con il frigorifero e la funzione potrebbe controllare il robot.

Una funzione ha normalmente un nome, parentesi e parentesi graffe. Come questo:

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

Si noti che /*...*/e //interrompere la lettura del codice dal browser.

NOME: puoi chiamare una funzione praticamente qualunque parola tu voglia. L'esempio "cookMeal" è tipico nell'unire due parole insieme e dare alla seconda una lettera maiuscola all'inizio, ma questo non è necessario. Non può avere uno spazio al suo interno e non può essere un numero da solo.

GENITORI: "Parentesi" o ()sono la cassetta delle lettere sulla porta della fabbrica della funzione JavaScript o una cassetta postale per la strada per l'invio di pacchetti di informazioni alla fabbrica. A volte la casella postale può essere contrassegnata, ad esempio cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime) , nel qual caso sai quali dati devi fornirli.

Bretelle: "Bretelle" che assomigliano a queste {}sono le finestre colorate della nostra fabbrica. Dall'interno della fabbrica puoi vedere, ma dall'esterno non puoi vedere dentro.

L'ESEMPIO DI LUNGO CODICE SOPRA

Il nostro codice inizia con la parola funzione , quindi sappiamo che è uno! Quindi il nome della funzione canta - questa è la mia descrizione di ciò che la funzione riguarda. Quindi parentesi () . Le parentesi sono sempre lì per una funzione. A volte sono vuoti, e, talvolta, hanno qualcosa in questo si ha una parola in.: (person). Dopo questo c'è un tutore come questo {. Questo segna l'inizio della funzione sing () . Ha un partner che segna la fine di sing () in questo modo}

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

Quindi questa funzione potrebbe avere qualcosa a che fare con il canto e potrebbe aver bisogno di alcuni dati su una persona. Contiene istruzioni per fare qualcosa con quei dati.

Ora, dopo la funzione sing () , vicino alla fine del codice è presente la riga

var person="an old lady";

VARIABILE: Le lettere var indicano "variabile". Una variabile è come una busta. All'esterno questa busta è contrassegnata come "persona". All'interno contiene un foglietto con le informazioni di cui la nostra funzione ha bisogno, alcune lettere e spazi uniti insieme come un pezzo di spago (si chiama spago) che formano una frase che legge "una vecchia signora". La nostra busta potrebbe contenere altri tipi di cose come numeri (chiamati numeri interi), istruzioni (chiamate funzioni), liste (chiamate matrici ). Poiché questa variabile è scritta al di fuori di tutte le parentesi graffe {}e poiché puoi vedere attraverso le finestre colorate quando ti trovi all'interno delle parentesi graffe, questa variabile può essere vista da qualsiasi parte del codice. Questa viene definita una "variabile globale".

VARIABILE GLOBALE: la persona è una variabile globale, nel senso che se cambi il suo valore da "una vecchia signora" a "un giovane", la persona continuerà ad essere un giovane fino a quando deciderai di cambiarlo di nuovo e che qualsiasi altra funzione in il codice può vedere che è un giovane. Premi il F12pulsante o osserva le impostazioni Opzioni per aprire la console per sviluppatori di un browser e digita "persona" per vedere qual è questo valore. Digitare person="a young man"per modificarlo e quindi digitare nuovamente "persona" per vedere che è cambiato.

Dopo questo abbiamo la linea

sing(person);

Questa linea chiama la funzione, come se stesse chiamando un cane

"Vieni a cantare , vieni a prendere una persona !"

Quando il browser ha caricato il codice JavaScript e ha raggiunto questa riga, avvierà la funzione. Ho messo la linea alla fine per assicurarmi che il browser abbia tutte le informazioni necessarie per eseguirlo.

Le funzioni definiscono le azioni - la funzione principale riguarda il canto. Contiene una variabile chiamata firstPart che si applica al canto della persona che si applica a ciascuno dei versi della canzone: "C'era" + persona + "che ha deglutito". Se digiti firstPart nella console, non otterrai una risposta perché la variabile è bloccata in una funzione: il browser non può vedere all'interno delle finestre colorate delle parentesi graffe.

CHIUSURE: Le chiusure sono le funzioni più piccole che si trovano all'interno della grande funzione sing () . Le piccole fabbriche all'interno della grande fabbrica. Ognuno di essi ha le proprie parentesi graffe, il che significa che le variabili al loro interno non possono essere viste dall'esterno. Ecco perché i nomi delle variabili ( creatura e risultato ) possono essere ripetuti nelle chiusure ma con valori diversi. Se digiti questi nomi di variabili nella finestra della console, non otterrai il suo valore perché è nascosto da due livelli di finestre colorate.

Tutte le chiusure sanno quale sia la variabile della funzione sing () chiamata firstPart , perché possono vedere dalle loro finestre colorate.

Dopo le chiusure arrivano le linee

fly();
spider();
bird();
cat();

La funzione sing () chiamerà ciascuna di queste funzioni nell'ordine in cui sono state fornite. Quindi verrà eseguito il lavoro della funzione sing ().


56

Va bene, parlando con un bambino di 6 anni, probabilmente userò le seguenti associazioni.

Immagina: stai giocando con i tuoi fratelli e sorelle in tutta la casa e ti muovi con i tuoi giocattoli e ne porti alcuni nella stanza di tuo fratello maggiore. Dopo un po 'tuo fratello tornò dalla scuola e andò nella sua stanza, e vi si chiuse dentro, quindi ora non si poteva più accedere ai giocattoli lasciati lì in modo diretto. Ma potresti bussare alla porta e chiedere a tuo fratello quei giocattoli. Questo si chiama chiusura del giocattolo ; tuo fratello l'ha inventato per te, e ora è nel campo di applicazione esterno .

Confronta con una situazione in cui una porta era chiusa da un tiraggio e nessuno all'interno (esecuzione di funzioni generali), e poi si verificava un incendio locale che bruciava la stanza (spazzatura: D), e poi veniva costruita una nuova stanza e ora puoi uscire un altro giocattolo lì (nuova istanza di funzione), ma non ottiene mai gli stessi giocattoli rimasti nella prima istanza della stanza.

Per un bambino avanzato metterei qualcosa di simile al seguente. Non è perfetto, ma ti fa sentire di cosa si tratta:

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

Come puoi vedere, i giocattoli lasciati nella stanza sono ancora accessibili tramite il fratello e non importa se la stanza è chiusa a chiave. Ecco un jsbin per giocarci.


49

Una risposta per un bambino di sei anni (supponendo che sappia cos'è una funzione, cos'è una variabile e quali sono i dati):

Le funzioni possono restituire dati. Un tipo di dati che è possibile restituire da una funzione è un'altra funzione. Quando viene restituita quella nuova funzione, tutte le variabili e gli argomenti utilizzati nella funzione che l'ha creata non scompaiono. Invece, quella funzione genitore "si chiude". In altre parole, nulla può guardarci dentro e vedere le variabili che ha usato tranne la funzione che ha restituito. Quella nuova funzione ha una capacità speciale di guardare indietro all'interno della funzione che l'ha creata e vedere i dati al suo interno.

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

Un altro modo davvero semplice per spiegarlo è in termini di portata:

Ogni volta che si crea un ambito più piccolo all'interno di un ambito più grande, l'ambito più piccolo sarà sempre in grado di vedere ciò che è nell'ambito più ampio.


49

Una funzione in JavaScript non è solo un riferimento a un insieme di istruzioni (come nel linguaggio C), ma include anche una struttura di dati nascosta che è composta da riferimenti a tutte le variabili non locali che utilizza (variabili acquisite). Tali funzioni in due parti sono chiamate chiusure. Ogni funzione in JavaScript può essere considerata una chiusura.

Le chiusure sono funzioni con uno stato. È in qualche modo simile a "questo" nel senso che "questo" fornisce anche lo stato per una funzione ma la funzione e "questo" sono oggetti separati ("questo" è solo un parametro elaborato e l'unico modo per legarlo permanentemente a un funzione è quella di creare una chiusura). Mentre "this" e la funzione vivono sempre separatamente, una funzione non può essere separata dalla sua chiusura e la lingua non fornisce alcun mezzo per accedere alle variabili acquisite.

Poiché tutte queste variabili esterne a cui fa riferimento una funzione nidificata in senso lessico sono in realtà variabili locali nella catena delle sue funzioni che racchiudono in modo lessicale (si può presumere che le variabili globali siano variabili locali di alcune funzioni radice) e ogni singola esecuzione di una funzione crea nuove istanze di le sue variabili locali, ne consegue che ogni esecuzione di una funzione che restituisce (o altrimenti trasferisce, come la registrazione come callback) una funzione nidificata crea una nuova chiusura (con il proprio set potenzialmente unico di variabili non locali referenziate che rappresentano la sua esecuzione contesto).

Inoltre, è necessario comprendere che le variabili locali in JavaScript non vengono create nel frame dello stack, ma nell'heap e distrutte solo quando nessuno le fa riferimento. Quando una funzione ritorna, i riferimenti alle sue variabili locali sono decrementati, ma possono comunque essere non nulli se durante l'esecuzione corrente sono diventati parte di una chiusura e sono ancora referenziati dalle sue funzioni annidate lessicamente (cosa che può accadere solo se i riferimenti a queste funzioni nidificate sono state restituite o trasferite in altro modo a un codice esterno).

Un esempio:

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();

47

Forse un po 'al di là di tutti, ma il più precoce dei bambini di sei anni, ma alcuni esempi che mi hanno aiutato a fare clic sul concetto di chiusura in JavaScript.

Una chiusura è una funzione che ha accesso all'ambito di un'altra funzione (le sue variabili e funzioni). Il modo più semplice per creare una chiusura è con una funzione all'interno di una funzione; il motivo è che in JavaScript una funzione ha sempre accesso all'ambito della sua funzione contenente.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

AVVISO: scimmia

Nell'esempio sopra, viene chiamato outerFunction che a sua volta chiama innerFunction. Nota come outerVar è disponibile per innerFunction, evidenziato dalla corretta segnalazione del valore di outerVar.

Ora considera quanto segue:

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

AVVISO: scimmia

referenceToInnerFunction è impostato su outerFunction (), che restituisce semplicemente un riferimento a innerFunction. Quando viene chiamato referenceToInnerFunction, restituisce outerVar. Ancora una volta, come sopra, questo dimostra che innerFunction ha accesso a outerVar, una variabile di outerFunction. Inoltre, è interessante notare che mantiene questo accesso anche dopo che outerFunction ha terminato l'esecuzione.

Ed ecco dove le cose diventano davvero interessanti. Se dovessimo sbarazzarci di outerFunction, diciamo di impostarlo su null, potresti pensare che referenceToInnerFunction perderebbe il suo accesso al valore di outerVar. Ma non è così.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

AVVISO: scimmia AVVISO: scimmia

Ma come va? Come può referenceToInnerFunction ancora conoscere il valore di outerVar ora che outerFunction è stato impostato su null?

Il motivo per cui referenceToInnerFunction può ancora accedere al valore di outerVar è perché quando la chiusura è stata creata posizionando innerFunction all'interno di outerFunction, innerFunction ha aggiunto un riferimento all'ambito di outerFunction (le sue variabili e funzioni) alla sua catena dell'ambito. Ciò significa che innerFunction ha un puntatore o un riferimento a tutte le variabili di outerFunction, incluso outerVar. Quindi, anche quando outerFunction ha terminato l'esecuzione, o anche se viene eliminato o impostato su null, le variabili nel suo ambito, come outerVar, restano in memoria a causa dell'eccezionale riferimento ad esse da parte di innerFunction a cui è stato restituito referenceToInnerFunction. Per rilasciare veramente la memoria di outerVar e il resto delle variabili di outerFunction dalla memoria, dovresti sbarazzarti di questo eccezionale riferimento a loro,

//////////

Altre due cose sulle chiusure da notare. Innanzitutto, la chiusura avrà sempre accesso agli ultimi valori della sua funzione di contenimento.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

AVVISO: gorilla

In secondo luogo, quando viene creata una chiusura, mantiene un riferimento a tutte le variabili e funzioni della sua funzione che racchiude; non arriva a scegliere. E tuttavia, le chiusure dovrebbero essere usate con parsimonia, o almeno con attenzione, poiché possono richiedere molta memoria; molte variabili possono essere mantenute in memoria molto tempo dopo che una funzione contenitiva ha terminato l'esecuzione.


45

Vorrei semplicemente indicarli alla pagina delle chiusure Mozilla . È la spiegazione migliore, più concisa e semplice delle basi della chiusura e dell'uso pratico che ho trovato. È altamente raccomandato a tutti coloro che imparano JavaScript.

E sì, lo consiglierei anche a un bambino di 6 anni - se il bambino di 6 anni sta imparando le chiusure, allora è logico che siano pronti a comprendere la spiegazione concisa e semplice fornita nell'articolo.


Sono d'accordo: la suddetta pagina di Mozilla è particolarmente semplice e concisa. Abbastanza sorprendentemente il tuo post non è stato apprezzato così tanto come gli altri.
Brice Coustillas,
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.