javascript chiusura - Come funzionano le chiusure JavaScript?




15 Answers

Chiusure di JavaScript per i principianti

Presentato da Morris su Tue, 21.02.2006 10:19. Pubblicato dalla community.

Le chiusure non sono magiche

Questa pagina spiega le chiusure in modo che un programmatore possa capirle - usando il codice JavaScript funzionante. Non è per guru o programmatori funzionali.

Le chiusure non sono difficili da capire una volta che il concetto di base è burrascoso. Tuttavia, è impossibile capire leggendo qualsiasi spiegazione teorica o accademicamente orientata!

Questo articolo è destinato ai programmatori con una certa esperienza di programmazione in un linguaggio tradizionale e che possono leggere la seguente funzione JavaScript:

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

Due brevi riassunti

  • Quando una funzione (foo) dichiara altre funzioni (bar e baz), la famiglia di variabili locali create in foo non viene distrutta quando la funzione viene chiusa. Le variabili diventano semplicemente invisibili al mondo esterno. Foo può quindi abilmente restituire la barra delle funzioni e baz, e possono continuare a leggere, scrivere e comunicare tra loro attraverso questa famiglia di variabili chiuse ("la chiusura") che nessun altro può intromettersi, nemmeno qualcuno che chiama foo di nuovo in futuro.

  • Una chiusura è un modo per supportare funzioni di prima classe ; è un'espressione che può fare riferimento a variabili all'interno del suo ambito (quando è stato dichiarato per la prima volta), essere assegnato a una variabile, essere passato come argomento a una funzione o essere restituito come risultato di una funzione.

Un esempio di chiusura

Il seguente codice restituisce un riferimento a una funzione:

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

La maggior parte dei programmatori JavaScript comprenderà come un riferimento a una funzione viene restituito a una variabile ( say2 ) nel codice precedente. Se non lo fai, allora devi guardarlo prima di poter imparare chiusure. Un programmatore che usa C pensa alla funzione come restituire un puntatore a una funzione e che le variabili say e say2 sono ciascuna un puntatore a una funzione.

Esiste una differenza fondamentale tra un puntatore C a una funzione e un riferimento JavaScript a una funzione. In JavaScript, puoi pensare a una variabile di riferimento di funzione che ha sia un puntatore a una funzione che un puntatore nascosto a una chiusura.

Il codice precedente ha una chiusura perché la funzione di function() { console.log(text); } anonima function() { console.log(text); } function() { console.log(text); } è dichiarato all'interno di un'altra funzione, ad sayHello2() in questo esempio. In JavaScript, se si utilizza la parola chiave function all'interno di un'altra funzione, si sta creando una chiusura.

In C e nella maggior parte degli altri linguaggi comuni, dopo che una funzione è tornata, tutte le variabili locali non sono più accessibili perché lo stack-frame è distrutto.

In JavaScript, se dichiari una funzione all'interno di un'altra funzione, le variabili locali della funzione esterna possono rimanere accessibili dopo essere ritornate da essa. Questo è dimostrato sopra, perché chiamiamo la funzione say2() dopo che siamo tornati da sayHello2() . Si noti che il codice che chiamiamo fa riferimento al text variabile, che era una variabile locale della funzione sayHello2() .

function() { console.log(text); } // Output of say2.toString();

Osservando l'output di say2.toString() , possiamo vedere che il codice si riferisce al text variabile. La funzione anonima può fare riferimento al text che contiene il valore 'Hello Bob' perché le variabili locali di sayHello2() sono state segretamente mantenute in vita in una chiusura.

Il genio è che in JavaScript un riferimento alla funzione ha anche un riferimento segreto alla chiusura in cui è stato creato - simile a come i delegati sono un puntatore del metodo più un riferimento segreto a un oggetto.

Altri esempi

Per qualche ragione, le chiusure sembrano davvero difficili da capire quando leggi su di esse, ma quando vedi alcuni esempi, diventa chiaro come funzionano (ci ho messo un po 'di tempo). Raccomando di esaminare attentamente gli esempi finché non capisci come funzionano. Se inizi a utilizzare le chiusure senza comprendere appieno il loro funzionamento, presto creerai alcuni bug molto strani!

Esempio 3

Questo esempio mostra che le variabili locali non vengono copiate - sono mantenute per riferimento. È come se il frame dello stack rimanga vivo nella memoria anche dopo che la funzione esterna esiste!

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

Esempio 4

Tutte e tre le funzioni globali hanno un riferimento comune alla stessa chiusura perché sono tutte dichiarate all'interno di una singola chiamata a setupSomeGlobals() .

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

Le tre funzioni hanno accesso condiviso alla stessa chiusura: le variabili locali di setupSomeGlobals() quando sono state definite le tre funzioni.

Nota che nell'esempio sopra, se richiami setupSomeGlobals() nuovamente, viene creata una nuova chiusura (stack-frame!). Le vecchie variabili gSetNumber , gSetNumber , gSetNumber vengono sovrascritte con nuove funzioni che hanno la nuova chiusura. (In JavaScript, ogni volta che si dichiara una funzione all'interno di un'altra funzione, le funzioni interne vengono / vengono ricreate di nuovo ogni volta che viene chiamata la funzione esterna.)

Esempio 5

Questo esempio mostra che la chiusura contiene tutte le variabili locali che sono state dichiarate all'interno della funzione esterna prima di uscire. Si noti che la variabile alice viene effettivamente dichiarata dopo la funzione anonima. La funzione anonima viene dichiarata per prima e quando viene chiamata tale funzione può accedere alla variabile alice perché alice trova nello stesso scope (JavaScript fa sollevamento delle variabili ). Inoltre sayAlice()() chiama direttamente il riferimento alla funzione restituito da sayAlice() - è esattamente lo stesso di quello che è stato fatto in precedenza ma senza la variabile temporanea.

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Tricky: nota anche che la variabile say è anche all'interno della chiusura, e potrebbe essere letta da qualsiasi altra funzione che potrebbe essere dichiarata all'interno di sayAlice() , oppure potrebbe essere letta in modo ricorsivo all'interno della funzione interna.

Esempio 6

Questo è un vero trucchetto per molte persone, quindi devi capirlo. Fai molta attenzione se stai definendo una funzione all'interno di un ciclo: le variabili locali della chiusura potrebbero non agire come potresti pensare prima.

Per comprendere questo esempio, devi comprendere la funzione di "sollevamento variabile" in Javascript.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

La linea result.push( function() {console.log(item + ' ' + list[i])} aggiunge un riferimento ad una funzione anonima tre volte alla matrice dei risultati.Se non si ha familiarità con le funzioni anonime, si pensi a piace:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

Notare che quando si esegue l'esempio, "item2 undefined" viene registrato tre volte! Questo perché, proprio come gli esempi precedenti, esiste una sola chiusura per le variabili locali per buildList (che sono result , i e item ). Quando le funzioni anonime sono chiamate sulla linea fnlist[j]() ; usano tutti la stessa chiusura singola e usano il valore corrente per i e l' item all'interno di quella chiusura (dove i un valore di 3 perché il ciclo è stato completato e l' item ha il valore di 'item2' ). Nota che stiamo indicizzando da 0 quindi l' item ha un valore di item2 . E l'i ++ incrementerà i al valore 3 .

Può essere utile vedere cosa succede quando viene utilizzata una dichiarazione a livello di blocco item variabile (tramite la parola chiave let ) invece di una dichiarazione di variabile dell'ambito della funzione tramite la parola chiave var . Se viene apportata questa modifica, ciascuna funzione anonima nel result dell'array ha la propria chiusura; quando viene eseguito l'esempio, l'output è il seguente:

item0 undefined
item1 undefined
item2 undefined

Se la variabile i è anche definita usando let invece di var , l'output è:

item0 1
item1 2
item2 3

Esempio 7

In questo ultimo esempio, ogni chiamata alla funzione principale crea una chiusura separata.

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

Sommario

Se tutto sembra completamente non chiaro, la cosa migliore da fare è giocare con gli esempi. Leggere una spiegazione è molto più difficile che comprendere esempi. Le mie spiegazioni su chiusure e riquadri, ecc. Non sono tecnicamente corrette: sono semplificazioni grossolane intese a facilitare la comprensione. Una volta terminata l'idea di base, puoi raccogliere i dettagli più tardi.

Punti finali:

  • Ogni volta che si utilizza la function all'interno di un'altra funzione, viene utilizzata una chiusura.
  • Ogni volta che si utilizza eval() all'interno di una funzione, viene utilizzata una chiusura. Il testo che si eval può fare riferimento a variabili locali della funzione, e all'interno di eval si possono anche creare nuove variabili locali usando eval('var foo = …')
  • Quando si utilizza la new Function(…) (il costruttore di funzioni ) all'interno di una funzione, non crea una chiusura. (La nuova funzione non può fare riferimento alle variabili locali della funzione esterna.)
  • Una chiusura in JavaScript è come mantenere una copia di tutte le variabili locali, proprio come erano quando una funzione veniva chiusa.
  • Probabilmente è meglio pensare che una chiusura venga sempre creata solo come una voce di una funzione, e le variabili locali vengono aggiunte a quella chiusura.
  • Un nuovo insieme di variabili locali viene mantenuto ogni volta che viene chiamata una funzione con una chiusura (dato che la funzione contiene una dichiarazione di funzione al suo interno, e un riferimento a quella funzione interna viene restituito o viene mantenuto un riferimento esterno per esso in qualche modo ).
  • Due funzioni potrebbero sembrare che abbiano lo stesso testo sorgente, ma hanno un comportamento completamente diverso a causa della loro chiusura "nascosta". Non penso che il codice JavaScript possa effettivamente scoprire se un riferimento di funzione ha una chiusura o meno.
  • Se si sta tentando di apportare modifiche al codice sorgente dinamico (ad esempio: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola')); ), non funzionerà se myFunction è una chiusura ( naturalmente, non si potrebbe nemmeno pensare di eseguire la sostituzione della stringa del codice sorgente in fase di esecuzione, ma ...).
  • È possibile ottenere dichiarazioni di funzioni all'interno di dichiarazioni di funzioni all'interno di funzioni e mash, e si possono ottenere chiusure a più di un livello.
  • Penso che normalmente una chiusura sia un termine sia per la funzione che per le variabili che vengono catturate. Nota che non uso quella definizione in questo articolo!
  • Sospetto che le chiusure in JavaScript differiscano da quelle normalmente presenti nei linguaggi funzionali.

link

Grazie

Se hai appena appreso chiusure (qui o altrove!), Allora sono interessato a qualsiasi tuo commento su eventuali modifiche che potresti suggerire che potrebbero rendere più chiaro questo articolo. Invia una mail a morrisjohns.com (morris_closure @). Si prega di notare che io non sono un guru su JavaScript, né sulle chiusure.

L'articolo originale di Morris può essere trovato nell'Archivio Internet .

closure in

Come spiegheresti le chiusure di 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 Scheme dato su Wikipedia, ma sfortunatamente non è stato d'aiuto.




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

Come il vecchio Albert ha detto: "Se non riesci a spiegarlo a un bambino di sei anni, davvero non lo capisci 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 abbia 6 anni e sia stranamente interessato a quell'argomento?

Sono abbastanza sicuro di essere stato una delle poche persone che ha tentato di rispondere alla domanda iniziale letteralmente. Da allora, la domanda è mutata più volte, quindi la mia risposta potrebbe sembrare incredibilmente stupida e fuori luogo. Speriamo che l'idea generale della storia rimanga divertente per alcuni.

Sono un grande fan dell'analogia e della metafora quando spieghiamo concetti difficili, quindi lasciami provare la mia mano 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 lei raccontava spesso la sua ultima incredibile avventura come principessa.

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

Ma tutto quello che vedrebbero è una bambina ...

var littleGirl = princess();

... raccontando storie di magia e fantasia.

littleGirl.story();

E anche se i grandi conoscevano le vere principesse, non avrebbero mai creduto negli unicorni o nei draghi perché non potevano mai vederli. Gli adulti dicevano che esistevano solo nell'immaginazione della bambina.

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

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




The Straw Man

Ho bisogno di sapere quante volte è stato cliccato un pulsante e fare qualcosa su 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 invaliderà lo scope esterno aggiungendo una variabile, il cui unico scopo è quello di tenere traccia del conteggio. In alcune situazioni, sarebbe preferibile in quanto l'applicazione esterna potrebbe aver bisogno di accedere a queste informazioni. Ma in questo caso, stiamo cambiando solo il comportamento di ogni terzo clic, quindi è preferibile racchiudere questa funzionalità all'interno del gestore di 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>

Notare alcune cose qui.

Nell'esempio sopra, sto usando il comportamento di chiusura di JavaScript. Questo comportamento consente a qualsiasi funzione di accedere all'ambito in cui è stato creato, a tempo indeterminato. Per applicare praticamente questo, 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), ne risulta un ambito privato per l'utilizzo da parte del risultante funzione ... Non è così semplice? Diluiamolo ...

Una semplice chiusura a una linea

//          _______________________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 al di fuori della 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 all'interno della chiusura e sempre disponibile per il gestore eventi, quindi mantiene il suo stato da click a click.

Inoltre, questo stato di variabile privata è completamente accessibile, sia per le letture che per l'assegnazione alle sue variabili scope private.

Ecco qua; ora stai incapsulando completamente questo comportamento.

Post completo sul blog (comprese le considerazioni su jQuery)




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

  • Una chiusura non viene creata solo quando si restituisce una funzione interna. Di fatto, la funzione di chiusura non ha bisogno di tornare affatto per poter creare la chiusura.Potresti invece assegnare la tua funzione interiore a una variabile in un ambito esterno o passarla come argomento a un'altra funzione in cui potrebbe essere richiamata 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é ogni funzione interna ha accesso a tale chiusura ogni volta che viene richiamata 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 queste variabili è l'ultimo valore al momento dell'accesso. Questo è il motivo per cui le funzioni interne create all'interno di loop possono essere complicate, dato che ognuna ha accesso alle stesse variabili esterne invece di afferrare una copia delle variabili nel momento in cui la funzione viene creata o chiamata.
  • Le "variabili" in una chiusura includono tutte le funzioni con nome dichiarate all'interno della funzione. Includono anche argomenti della funzione. Una chiusura ha anche accesso alle sue variabili di chiusura contenenti, fino allo scopo globale.
  • Le chiusure utilizzano la memoria, ma non causano perdite di memoria dal momento che JavaScript di per sé ripulisce le proprie strutture circolari che non sono referenziate. Le perdite di memoria di Internet Explorer che coinvolgono le chiusure vengono create quando non riesce a disconnettere i valori degli attributi del DOM che fanno riferimento alle chiusure, mantenendo quindi i riferimenti a possibili strutture circolari.



Una chiusura è molto simile a un oggetto. Viene istanziato ogni volta che si chiama una funzione.

Lo scopo di una chiusura in JavaScript è lessicale, il che significa che tutto ciò che è contenuto nella funzione a cui appartiene la chiusura , ha accesso a qualsiasi variabile che è in essa.

Una variabile è contenuta nella chiusura se tu

  1. assegnarlo con var foo=1; o
  2. Scrivi e basta var foo;

Se una funzione interna (una funzione contenuta in un'altra funzione) accede a tale variabile senza definirla nel suo ambito con var, modifica il contenuto della variabile nella chiusura esterna .

Una chiusura sopravvive al runtime della funzione che lo ha generato. Se altre funzioni escono dalla chiusura / ambito in cui sono definite (ad esempio come valori di ritorno), quelle continueranno a fare riferimento a tale chiusura .

Esempio

function example(closure) {
  // define somevariable to live in the closure of example
  var somevariable = 'unchanged';

  return {
    change_to: function(value) {
      somevariable = value;
    },
    log: function(value) {
      console.log('somevariable of closure %s is: %s',
        closure, somevariable);
    }
  }
}

closure_one = example('one');
closure_two = example('two');

closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();

Produzione

somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged



Le chiusure sono semplici:

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

Ecco una fabbrica che produce calcolatrici 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 per make_calculatorcreare una nuova variabile locale n, che continua ad essere utilizzabile da quella calcolatrice adde multiplyfunziona molto dopo i make_calculatorritorni.

Se hai familiarità con gli stack frame, questi calcolatori sembrano strani: come possono continuare ad accedere ndopo i make_calculatorresi? La risposta è immaginare che JavaScript non usi "stack frames", ma usi invece "heap frames", che possono persistere dopo la chiamata alla funzione che li ha fatti restituire.

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 nell'articolo "Chiusure for Dummies" fornito in un'altra risposta , eccetto l'esempio 6, che mostra semplicemente che le variabili possono essere utilizzate prima di essere dichiarate, un fatto piacevole da conoscere ma completamente estraneo alle chiusure. Copre anche tutti i punti nella risposta accettata , ad eccezione dei punti (1) che le funzioni copiano i loro argomenti in variabili locali (gli argomenti della funzione con nome), e (2) che i numeri di copia creano un nuovo numero, ma copiando un riferimento oggetto ti dà un altro riferimento allo stesso oggetto. Questi sono anche buoni da sapere ma ancora completamente estranei alle chiusure. È anche molto simile all'esempio di questa risposta, ma un po 'più breve e meno astratto. Non copre il punto diquesta risposta o questo commento , ovvero che JavaScript rende difficile collegare la correntevalore di una variabile di ciclo nella tua funzione interna: il passaggio "plug-in" può essere fatto solo con una funzione di aiuto che racchiude la tua funzione interna e viene invocata su ogni iterazione del ciclo. (In senso stretto, la funzione interna accede alla copia della variabile della funzione di aiuto, piuttosto che avere qualcosa collegato.) Ancora una volta, molto utile quando si creano chiusure, ma non fa parte di cosa sia una chiusura o come funzioni. Vi è ulteriore confusione dovuta alle chiusure che funzionano in modo diverso nei linguaggi funzionali come ML, dove 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 (cioè il modo "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 più nodi sono nidificati o anche nel contesto globale, come indica chiaramente questa risposta .




Puoi spiegare chiusure a un bambino di 5 anni? *

Continuo a pensare 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);​

* Domanda AC #




Wikipedia sulle chiusure :

Nell'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 a variabili definite nell'ambito circostante.

Poiché la costruzione di definizione di scope in JavaScript è una funzione , non un blocco di codice come in molti altri linguaggi, ciò che di solito intendiamo per chiusura in JavaScript è una funzione che funziona con variabili non locali definite nella funzione circostante già eseguita .

Le chiusure sono 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 precedente utilizza una funzione anonima, che è stata eseguita una volta. Ma non deve essere. Può essere chiamato (es. mkdb) Ed eseguito successivamente, generando una funzione di database ogni volta che viene richiamato. Ogni funzione generata avrà il proprio oggetto di database nascosto. Un altro esempio di utilizzo delle chiusure è quando non restituiamo una funzione, ma un oggetto che contiene più funzioni per scopi diversi, ognuna delle quali ha accesso agli stessi dati.




I bambini ricorderanno sempre i segreti che hanno condiviso con i loro genitori, anche dopo che i loro genitori se ne sono andati. Questo è ciò che le chiusure sono 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 luogo segreto in cui vengono memorizzate. Potrebbe essere chiamato Chamber of Secrets o stack o scope locale ma non ha molta importanza. Sappiamo che sono lì, da qualche parte, nascosti nella memoria.

Ma in JavaScript c'è questa cosa molto speciale che le funzioni che sono create all'interno di altre funzioni, possono anche conoscere le variabili locali dei loro genitori e tenerle 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, finché 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 finirà, 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 sta più correndo", il ricordo di lei non è perso e il suo bambino ricorderà sempre il suo nome e altri segreti che hanno condiviso durante il loro tempo insieme.

Quindi, se chiami il bambino "Alice", lei risponderà

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

Questo è tutto quello che c'è da dire.




Esempio per il primo punto di dlaliberte:

Una chiusura non viene creata solo quando si restituisce una funzione interna. In effetti, la funzione di chiusura non ha bisogno di tornare affatto. Potresti invece assegnare la tua funzione interiore 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 inclusione 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);



So che ci sono già molte soluzioni, ma immagino che questo script piccolo e semplice 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



Le funzioni JavaScript possono accedere a:

  1. argomenti
  2. Locali (cioè 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, la funzione è una chiusura.

Si noti che le funzioni esterne non sono necessarie, anche se offrono vantaggi di cui non discuto qui. Accedendo ai dati nel proprio ambiente, una chiusura mantiene vivi i dati. Nella sottocasi delle funzioni esterne / interne, una funzione esterna può creare dati locali ed eventualmente uscire, e tuttavia, se una o più funzioni interne sopravvivono dopo che la funzione esterna è terminata, la funzione interna (s) mantiene i dati locali della funzione esterna vivo.

Esempio di chiusura che utilizza l'ambiente globale:

Immagina che gli eventi Button Overflow Vote-Up e Vote-Down siano implementati come chiusure, voteUp_click e voteDown_click, che hanno accesso a variabili esterne isVotedUp e isVotedDown, che sono definite globalmente. (Per ragioni di semplicità, mi riferisco ai pulsanti di votazione delle domande di , non alla serie di pulsanti Rispondi voto.)

Quando l'utente fa clic sul pulsante VoteUp, la funzione voteUp_click verifica se isVotedDown == true per determinare se votare o semplicemente annullare un voto basso. 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 in quanto tutte accedono al loro ambiente.




Come padre di un bambino di 6 anni, attualmente insegnando ai bambini piccoli (e un parente inesperto alla codifica senza istruzione formale, saranno necessarie correzioni), penso che la lezione si attenga meglio attraverso il gioco pratico. Se il bambino di 6 anni è pronto a capire che cos'è una chiusura, allora è abbastanza grande per andare da solo. Suggerirei di incollare il codice in jsfiddle.net, spiegandone un po 'e lasciandoli da soli a inventare una canzone unica. Il testo esplicativo di seguito è 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. Può essere numeri, parole, misure, osservazioni o anche solo descrizioni di cose. Non puoi toccarlo, annusarlo o assaggiarlo. Puoi scriverlo, parlarlo e ascoltarlo. Potresti usarlo per creare l' odore e il sapore del tocco usando un computer. Può essere reso utile da un computer usando il codice.

CODICE: tutta la scrittura sopra è chiamata codice . È scritto in JavaScript.

JAVASCRIPT: JavaScript è una lingua. Come l'inglese o il francese o il cinese sono le lingue. Esistono molte lingue comprese dai computer e da altri processori elettronici. Perché JavaScript sia compreso da un computer ha bisogno di un interprete. Immagina se un insegnante che parla solo russo venga ad insegnare la tua classe a scuola. Quando l'insegnante dice "все садятся", la classe non capirebbe. Ma per fortuna hai un alunno russo nella tua classe che dice a tutti che questo significa "tutti si siedono" - così tutti voi. La classe è come un computer e l'allievo russo è l'interprete. Per JavaScript l'interprete più comune è chiamato browser.

BROWSER: quando ti connetti a Internet su un computer, tablet o telefono per visitare un sito web, utilizzi 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 una sola macchina al suo interno. Oppure potrebbe contenere molte altre piccole fabbriche, ognuna con molte macchine che svolgono diversi lavori. In una vera e propria fabbrica di vestiti per la vita potresti avere delle risme di stoffa e di fili di filo infilati e t-shirt e jeans che escono. La nostra fabbrica JavaScript elabora solo i dati, non può cucire, praticare un foro o fondere il metallo. Nei nostri dati di fabbrica JavaScript entra e i dati vengono fuori.

Tutta questa roba di dati sembra un po 'noiosa, ma è davvero molto interessante; 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. Abbiamo bisogno di fornire la funzione con i dati su ciò che ci piace, la funzione potrebbe 'parlare' al frigorifero e la funzione potrebbe controllare il robot.

Normalmente una funzione ha un nome, parentesi e parentesi graffe. Come questo:

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

Si noti che /*...*/e il //codice di arresto viene letto dal browser.

NOME: puoi chiamare una funzione qualunque sia la parola che vuoi. L'esempio "cookMeal" è tipico nell'unire due parole insieme e dare alla seconda una lettera maiuscola all'inizio, ma questo non è necessario. Non può contenere uno spazio e non può essere un numero a sé stante.

PARENTHESES: "Parentesi" o ()casella postale sulla porta della factory di funzioni JavaScript o casella postale in strada per l'invio di pacchetti di informazioni alla fabbrica. A volte la casella postale potrebbe essere contrassegnata per esempio cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime) , nel qual caso si sa quali dati si devono dare.

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

L'ESEMPIO DI CODICE LUNGO SOPRA

Il nostro codice inizia con la funzione parola , quindi sappiamo che è uno! Quindi il nome della funzione sing - questa è la mia descrizione di cosa tratta la funzione. 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 cantare () come questo}

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. Ha delle istruzioni dentro per fare qualcosa con quei dati.

Ora, dopo la funzione sing () , vicino alla fine del codice c'è la linea

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 di carta con le informazioni di cui la nostra funzione ha bisogno, alcune lettere e spazi uniti come un pezzo di corda (si chiama una stringa) che fa una frase che legge "una vecchia signora". La nostra busta potrebbe contenere altri tipi di cose come numeri (chiamati interi), istruzioni (chiamate funzioni), elenchi (chiamati array ). Poiché questa variabile è scritta al di fuori di tutte le parentesi graffe {}e poiché è possibile vedere attraverso le finestre colorate quando ci si trova all'interno delle parentesi graffe, questa variabile può essere vista da qualsiasi parte del codice. La chiamiamo una "variabile globale".

VARIABILE GLOBALE: la persona è una variabile globale, il che significa che se cambi il suo valore da "una vecchia signora" a "un giovane uomo", la persona continuerà ad essere un giovane finché non deciderai di cambiarla di nuovo e qualsiasi altra funzione in il codice può vedere che è un giovane uomo. Premi il F12pulsante o guarda le impostazioni di Opzioni per aprire la console di sviluppo di un browser e digitare "persona" per vedere qual è questo valore. Digita person="a young man"per cambiarlo e quindi digita "person" di nuovo 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 la persona !"

Quando il browser ha caricato il codice JavaScript e ha raggiunto questa linea, avvierà la funzione. Metto 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 inghiottì". Se si digita firstPart nella console, non si otterrà 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 funzione big sing () . Le piccole fabbriche all'interno della grande fabbrica. Ognuno di loro ha le proprie parentesi, 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.

Le chiusure sanno tutte 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à ognuna di queste funzioni nell'ordine in cui vengono fornite. Quindi il lavoro della funzione sing () verrà eseguito.




Una risposta per un bambino di sei anni (ammesso che sappia cos'è una funzione e che 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 la nuova funzione, tutte le variabili e gli argomenti utilizzati nella funzione che lo ha creato non scompaiono. Invece, quella funzione genitore "si chiude". In altre parole, nulla può guardare al suo interno e vedere le variabili utilizzate tranne la funzione restituita. Quella nuova funzione ha una capacità speciale di guardare indietro all'interno della funzione che lo ha creato 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 molto semplice per spiegarlo è in termini di ambito:

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




Forse un po 'al di là di tutto tranne il più precoce dei bambini di sei anni, ma alcuni esempi che hanno contribuito a rendere il concetto di chiusura in JavaScript per me.

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 di contenimento.

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

outerFunction();

AVVISO: scimmia

Nell'esempio precedente, viene chiamata outerFunction che a sua volta chiama innerFunction. Nota come outerVar è disponibile per innerFunction, evidenziato dal suo corretto avviso del valore di outerVar.

Considerare ora 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 chiamata referenceToInnerFunction, restituisce outerVar. Di nuovo, come sopra, questo dimostra che innerFunction ha accesso a outerVar, una variabile di ExternalFunction. Inoltre, è interessante notare che mantiene questo accesso anche dopo che ExternalFunction ha terminato l'esecuzione.

E qui è dove le cose diventano davvero interessanti. Se dovessimo sbarazzarci di outerFunction, diciamo impostalo su null, potresti pensare che referenceToInnerFunction perderà il suo accesso al valore di outerVar. Ma questo non è il caso.

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

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

outerFunction = null;
alert(referenceToInnerFunction());

AVVISO: scimmia ALERT: scimmia

Ma come è così? In che modo referenceToInnerFunction può ancora conoscere il valore di outerVar ora che externalFunction è stato impostato su null?

La ragione per cui referenceToInnerFunction può ancora accedere al valore di outerVar è perché quando la chiusura è stata creata ponendo innerFunction all'interno di outerFunction, innerFunction ha aggiunto un riferimento all'ambito di externalFunction (le sue variabili e funzioni) alla sua catena di scope. Ciò significa che innerFunction ha un puntatore o riferimento a tutte le variabili di ExternalFunction, incluso outerVar. Quindi, anche quando outerFunction ha terminato l'esecuzione, o anche se è cancellato o impostato su null, le variabili nel suo ambito, come outerVar, rimangono in memoria a causa del riferimento eccezionale a loro sulla parte della innerFunction che è stata restituita a referenceToInnerFunction. Per liberare veramente outerVar e il resto delle variabili di External function dalla memoria, dovresti sbarazzarti di questo eccezionale riferimento a loro,dite impostando referenceToInnerFunction su null.

//////////

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 sue variabili e funzioni della funzione di chiusura; non arriva a scegliere. E tuttavia, le chiusure dovrebbero essere usate con parsimonia, o almeno con attenzione, dato che possono essere intensivi della memoria; molte variabili possono essere conservate in memoria molto tempo dopo che una funzione di contenimento ha terminato l'esecuzione.




Related

javascript function variables scope closures