Che cos'è una "chiusura"?


432

Ho fatto una domanda su Currying e sono state menzionate le chiusure. Cos'è una chiusura? Come si collega al curry?


22
Ora qual è esattamente la chiusura ??? Alcune risposte dicono, la chiusura è la funzione. Alcuni dicono che è lo stack. Alcune risposte dicono, è il valore "nascosto". Per la mia comprensione, è la funzione + variabili racchiuse.
Roland,

3
Spiega ciò che una chiusura è: stackoverflow.com/questions/4103750/...
dietbuddha

Dai un'occhiata anche a Cos'è una chiusura? at softwareengineering.stackexchange
B12 Toaster

Illustra cos'è una chiusura e il caso d'uso comune: trungk18.com/experience/javascript-closure
Sasuke91

Risposte:


744

Ambito variabile

Quando si dichiara una variabile locale, quella variabile ha un ambito. In genere, le variabili locali esistono solo all'interno del blocco o della funzione in cui vengono dichiarate.

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

Se provo ad accedere a una variabile locale, la maggior parte delle lingue la cercherà nell'ambito corrente, quindi passa attraverso gli ambiti padre fino a raggiungere l'ambito radice.

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

Al termine di un blocco o di una funzione, le sue variabili locali non sono più necessarie e di solito vengono eliminate dalla memoria.

È così che normalmente ci aspettiamo che le cose funzionino.

Una chiusura è un ambito variabile locale persistente

Una chiusura è un ambito persistente che si aggrappa alle variabili locali anche dopo che l'esecuzione del codice è stata spostata da quel blocco. Le lingue che supportano la chiusura (come JavaScript, Swift e Ruby) ti permetteranno di mantenere un riferimento a un ambito (compresi i relativi ambiti padre), anche dopo che il blocco in cui sono state dichiarate quelle variabili ha terminato l'esecuzione, a condizione che tu mantenga un riferimento a quel blocco o funzione da qualche parte.

L'oggetto scope e tutte le sue variabili locali sono legate alla funzione e persisteranno finché persiste tale funzione.

Questo ci dà la portabilità delle funzioni. Possiamo aspettarci che qualsiasi variabile che fosse nell'ambito quando la funzione è stata definita per la prima volta sia ancora nell'ambito quando in seguito chiamiamo la funzione, anche se la chiamiamo in un contesto completamente diverso.

Per esempio

Ecco un esempio davvero semplice in JavaScript che illustra il punto:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

Qui ho definito una funzione all'interno di una funzione. La funzione interna ottiene l'accesso a tutte le variabili locali della funzione esterna, incluso a. La variabile arientra nell'ambito della funzione interna.

Normalmente quando una funzione esce, tutte le sue variabili locali vengono spazzate via. Tuttavia, se restituiamo la funzione interna e la assegniamo a una variabile in fncmodo che persista dopo la outersua uscita, anche tutte le variabili che erano nell'ambito nell'ambito della innerdefinizione erano persistenti . La variabile aè stata chiusa, è all'interno di una chiusura.

Si noti che la variabile aè totalmente privata fnc. Questo è un modo per creare variabili private in un linguaggio di programmazione funzionale come JavaScript.

Come potresti immaginare, quando lo chiamo fnc()stampa il valore di a, che è "1".

In una lingua senza chiusura, la variabile asarebbe stata raccolta e gettata via quando la funzione era outeruscita. Chiamare fnc avrebbe generato un errore perché anon esiste più.

In JavaScript, la variabile apersiste perché l'ambito della variabile viene creato quando la funzione viene dichiarata per la prima volta e persiste finché la funzione continua ad esistere.

aappartiene allo scopo di outer. L'ambito di innerha un puntatore genitore all'ambito di outer. fncè una variabile che punta a inner. apersiste finché fncpersiste. aè all'interno della chiusura.


116
Ho pensato che fosse un esempio abbastanza buono e facile da capire.
user12345613,

16
Grazie per la fantastica spiegazione, ne ho visti molti ma questo è il momento in cui l'ho capito davvero.
Dimitar Dimitrov,

2
Potrei avere un esempio di come funziona in una libreria come JQuery come indicato nel secondo all'ultimo paragrafo? Non l'ho capito del tutto.
DPM,

6
Ciao Jubbat, sì, apri jquery.js e dai un'occhiata alla prima riga. Vedrai che è stata aperta una funzione. Ora salta alla fine, vedrai window.jQuery = window. $ = JQuery. Quindi la funzione viene chiusa e auto-eseguita. Ora hai accesso alla funzione $, che a sua volta ha accesso alle altre funzioni definite nella chiusura. Questo risponde alla tua domanda?
Superluminario,

4
Migliore spiegazione sul web. Molto più semplice di quanto pensassi
Mantis,

95

Faccio un esempio (in JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

Ciò che questa funzione, makeCounter, fa è che restituisce una funzione, che abbiamo chiamato x, che verrà conteggiata da una ogni volta che viene chiamata. Dato che non stiamo fornendo alcun parametro a x, in qualche modo deve ricordare il conteggio. Sa dove trovarlo in base a quello che viene chiamato scoping lessicale - deve cercare il punto in cui è definito per trovare il valore. Questo valore "nascosto" è ciò che viene chiamato chiusura.

Ecco di nuovo il mio esempio di curry:

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

var add3 = add(3);

add3(4); returns 7

Quello che puoi vedere è che quando chiami aggiungi con il parametro a (che è 3), quel valore è contenuto nella chiusura della funzione restituita che stiamo definendo add3. In questo modo, quando chiamiamo add3, sa dove trovare il valore a per eseguire l'aggiunta.


4
IDK, quale lingua (probabilmente F #) hai usato nella lingua sopra. Potresti per favore dare l'esempio sopra in pseudocodice? Non riesco a capirlo.
utente


3
@KyleCronin Ottimo esempio, grazie. D: È più corretto dire "il valore nascosto si chiama chiusura" o "la funzione che nasconde il valore è la chiusura"? O "il processo di nascondere il valore è la chiusura"? Grazie!

2
@RobertHume Buona domanda. Semanticamente, il termine "chiusura" è alquanto ambiguo. La mia definizione personale è che la combinazione del valore nascosto e dell'uso della funzione che lo racchiude costituisce la chiusura.
Kyle Cronin,

1
@KyleCronin Grazie - ho un programma a medio termine lunedì. :) Volevo avere il concetto di "chiusura" solido nella mia testa. Grazie per aver pubblicato questa ottima risposta alla domanda di OP!

58

La risposta di Kyle è abbastanza buona. Penso che l'unico ulteriore chiarimento sia che la chiusura è fondamentalmente un'istantanea dello stack nel punto in cui viene creata la funzione lambda. Quindi, quando la funzione viene rieseguita, lo stack viene ripristinato in quello stato prima di eseguire la funzione. Quindi, come menziona Kyle, quel valore nascosto ( count) è disponibile quando viene eseguita la funzione lambda.


14
Non è solo lo stack: sono gli ambiti lessicali racchiusi che vengono conservati, indipendentemente dal fatto che siano memorizzati nello stack o nell'heap (o in entrambi).
Matt Fenwick,

38

Prima di tutto, contrariamente a quanto la maggior parte delle persone qui ti dice, la chiusura non è una funzione ! Così che cosa è esso?
È un insieme di simboli definiti nel "contesto circostante" di una funzione (noto come il suo ambiente ) che lo rendono un'espressione CHIUSA (cioè un'espressione in cui ogni simbolo è definito e ha un valore, quindi può essere valutato).

Ad esempio, quando si dispone di una funzione JavaScript:

function closed(x) {
  return x + 3;
}

è un'espressione chiusa perché tutti i simboli presenti in essa sono definiti in essa (i loro significati sono chiari), quindi puoi valutarla. In altre parole, è autonomo .

Ma se hai una funzione come questa:

function open(x) {
  return x*y + 3;
}

è un'espressione aperta perché in essa sono presenti simboli che non sono stati definiti in essa. Vale a dire, y. Quando osserviamo questa funzione, non possiamo dire cosa ysia e cosa significhi, non ne conosciamo il valore, quindi non possiamo valutare questa espressione. Cioè non possiamo chiamare questa funzione finché non diciamo cosa ydovrebbe significare in essa. Questa ysi chiama variabile libera .

Ciò yrichiede una definizione, ma questa definizione non fa parte della funzione - è definita da qualche altra parte, nel suo "contesto circostante" (noto anche come ambiente ). Almeno questo è ciò che speriamo: P

Ad esempio, potrebbe essere definito a livello globale:

var y = 7;

function open(x) {
  return x*y + 3;
}

Oppure potrebbe essere definito in una funzione che lo avvolge:

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

La parte dell'ambiente che dà alle variabili libere in un'espressione il loro significato, è la chiusura . Si chiama in questo modo, perché trasforma un'espressione aperta in un'espressione chiusa , fornendo queste definizioni mancanti per tutte le sue variabili libere , in modo da poterle valutare.

Nell'esempio sopra, la funzione interna (che non abbiamo dato un nome perché non ne avevamo bisogno) è un'espressione aperta perché la variabile yin essa è libera - la sua definizione è al di fuori della funzione, nella funzione che la avvolge . L' ambiente per quella funzione anonima è l'insieme di variabili:

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Ora, la chiusura è quella parte di questo ambiente che chiude la funzione interna fornendo le definizioni per tutte le sue variabili libere . Nel nostro caso, l'unica variabile libera nella funzione interna era y, quindi la chiusura di quella funzione è questo sottoinsieme del suo ambiente:

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Gli altri due simboli definiti nell'ambiente non fanno parte della chiusura di quella funzione, perché non richiede l'esecuzione. Non sono necessari per chiuderlo .

Maggiori informazioni sulla teoria alla base di questo qui: https://stackoverflow.com/a/36878651/434562

Vale la pena notare che nell'esempio sopra, la funzione wrapper restituisce la sua funzione interna come valore. Il momento in cui chiamiamo questa funzione può essere remoto nel tempo dal momento in cui la funzione è stata definita (o creata). In particolare, la sua funzione di wrapping non è più in esecuzione e i suoi parametri che sono stati nello stack di chiamate non sono più presenti: P Questo crea un problema, perché la funzione interna deve yessere lì quando viene chiamata! In altre parole, richiede le variabili dalla sua chiusura per sopravvivere in qualche modo alla funzione wrapper ed essere lì quando necessario. Pertanto, la funzione interna deve creare un'istantanea di queste variabili che ne rendono la chiusura e conservale in un luogo sicuro per un uso successivo. (Da qualche parte fuori dallo stack di chiamate.)

E questo è il motivo per cui le persone spesso confondono il termine chiusura come quel tipo speciale di funzione che può fare tali istantanee delle variabili esterne che usano, o la struttura di dati usata per memorizzare queste variabili per dopo. Ma spero che si capisce ora che sono non la chiusura in sé - sono solo modi per implementare le chiusure in un linguaggio di programmazione, o di meccanismi linguistici che permette le variabili dalla chiusura della funzione di essere lì quando necessario. Ci sono molte idee sbagliate intorno alle chiusure che (inutilmente) rendono questo argomento molto più confuso e complicato che in realtà sia.


1
Un'analogia che potrebbe aiutare i principianti a questo è una chiusura che lega tutte le estremità libere , che è ciò che una persona fa quando cerca la chiusura (o risolve tutti i riferimenti necessari, o ...). Bene, mi ha aiutato a pensarlo in questo modo: o)
Will Crawford,

Ho letto molte definizioni di chiusura nel corso degli anni, ma penso che questa sia la mia preferita finora. Immagino che tutti noi abbiamo il nostro modo di mappare mentalmente concetti come questo e questo molto con i miei.
Jason S.

29

Una chiusura è una funzione che può fare riferimento allo stato in un'altra funzione. Ad esempio, in Python, utilizza la chiusura "interna":

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1

23

Per facilitare la comprensione delle chiusure potrebbe essere utile esaminare come potrebbero essere implementate in un linguaggio procedurale. Questa spiegazione seguirà un'implementazione semplicistica di chiusure in Scheme.

Per iniziare, devo introdurre il concetto di spazio dei nomi. Quando si immette un comando in un interprete Scheme, è necessario valutare i vari simboli nell'espressione e ottenere il loro valore. Esempio:

(define x 3)

(define y 4)

(+ x y) returns 7

Le espressioni definite memorizzano il valore 3 nello spot per x e il valore 4 nello spot per y. Quindi quando chiamiamo (+ xy), l'interprete cerca i valori nello spazio dei nomi ed è in grado di eseguire l'operazione e restituire 7.

Tuttavia, nello Schema ci sono espressioni che ti consentono di sovrascrivere temporaneamente il valore di un simbolo. Ecco un esempio:

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

Quello che fa la parola chiave let è l'introduzione di un nuovo spazio dei nomi con x come valore 5. Noterai che è ancora in grado di vedere che y è 4, rendendo la somma restituita come 9. Puoi anche vedere che una volta che l'espressione è terminata x è tornato ad essere 3. In questo senso, x è stato temporaneamente mascherato dal valore locale.

I linguaggi procedurali e orientati agli oggetti hanno un concetto simile. Ogni volta che dichiari una variabile in una funzione che ha lo stesso nome di una variabile globale, ottieni lo stesso effetto.

Come lo implementeremmo? Un modo semplice è con un elenco collegato: la testa contiene il nuovo valore e la coda contiene il vecchio spazio dei nomi. Quando devi cercare un simbolo, inizi dalla testa e scendi lungo la coda.

Passiamo ora all'implementazione di funzioni di prima classe per il momento. Più o meno, una funzione è un insieme di istruzioni da eseguire quando la funzione viene chiamata culminante nel valore restituito. Quando leggiamo in una funzione, possiamo memorizzare queste istruzioni dietro le quinte ed eseguirle quando viene chiamata la funzione.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

Definiamo x come 3 e più-x come parametro, y, più il valore di x. Alla fine chiamiamo plus-x in un ambiente in cui x è stato mascherato da una nuova x, questa ha valore 5. Se memorizziamo semplicemente l'operazione, (+ xy), per la funzione plus-x, poiché siamo nel contesto di x essendo 5 il risultato restituito sarebbe 9. Questo è ciò che si chiama scoping dinamico.

Tuttavia, Scheme, Common Lisp e molte altre lingue hanno quello che viene chiamato scoping lessicale - oltre a memorizzare l'operazione (+ xy) memorizziamo anche lo spazio dei nomi in quel particolare punto. In questo modo, quando osserviamo i valori, possiamo vedere che x, in questo contesto, è davvero 3. Questa è una chiusura.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

In breve, possiamo usare un elenco collegato per memorizzare lo stato dello spazio dei nomi al momento della definizione della funzione, permettendoci di accedere alle variabili dagli ambiti racchiusi, oltre a fornirci la possibilità di mascherare localmente una variabile senza influire sul resto del programma.


ok, grazie alla tua risposta, penso di aver finalmente idea di cosa si tratti di chiusura. Ma c'è una grande domanda: "possiamo usare un elenco collegato per memorizzare lo stato dello spazio dei nomi al momento della definizione della funzione, permettendoci di accedere a variabili che altrimenti non sarebbero più nell'ambito." Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Lazer,

@Laser: Spiacente, quella frase non aveva molto senso, quindi l'ho aggiornata. Spero che abbia più senso ora. Inoltre, non pensare all'elenco collegato come un dettaglio dell'implementazione (in quanto è molto inefficiente) ma come un modo semplice di concettualizzare come potrebbe essere fatto.
Kyle Cronin,

10

Ecco un esempio reale del motivo per cui Closures prende a calci in culo ... Questo è direttamente dal mio codice Javascript. Vorrei illustrare.

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

Ed ecco come lo useresti:

var startPlayback = function(track) {
  Player.play(track);  
};
startPlayback(someTrack);

Ora immagina di voler ritardare la riproduzione, ad esempio 5 secondi dopo l'esecuzione di questo frammento di codice. Bene, è facile con delayed è la chiusura:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

Quando chiami delaycon 5000ms, viene eseguito il primo frammento e memorizza gli argomenti passati nella sua chiusura. Quindi, 5 secondi dopo, quando si verifica il setTimeoutcallback, la chiusura mantiene ancora quelle variabili, quindi può chiamare la funzione originale con i parametri originali.
Questo è un tipo di curry o decorazione di funzioni.

Senza chiusure, dovresti in qualche modo mantenere lo stato di quelle variabili al di fuori della funzione, sporcando così il codice al di fuori della funzione con qualcosa che logicamente appartiene al suo interno. L'uso delle chiusure può migliorare notevolmente la qualità e la leggibilità del codice.


1
Va notato che l'estensione del linguaggio o degli oggetti host è generalmente considerata una cosa negativa in quanto fanno parte dello spazio dei nomi globale
Jon Cooke,

9

Le funzioni che non contengono variabili libere sono chiamate funzioni pure.

Le funzioni contenenti una o più variabili libere sono chiamate chiusure.

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure


Perché questo è svantaggiato? In realtà è molto più "sulla strada giusta" con quella distinzione in variabili libere e variabili legate, funzioni pure / chiuse e funzioni impure / aperte, rispetto alla maggior parte delle altre risposte all'oscuro qui presenti: P (sconto per chiusure confuse con funzioni essere chiuso).
SasQ,

Non ho nessuna idea, davvero. Questo è il motivo per cui StackOverflow fa schifo. Guarda la fonte della mia risposta. Chi avrebbe da ridire su questo?
soundyogi,

SO non fa schifo e non ho mai sentito parlare del termine "variabile libera"
Kai

È difficile parlare di chiusure senza menzionare variabili libere. Basta cercarli. Terminologia CS standard.
ComDubh,

"Le funzioni che contengono una o più variabili libere sono chiamate chiusure" non è una definizione corretta - le chiusure sono sempre oggetti di prima classe.
ComDubh,

7

tl; dr

Una chiusura è una funzione e il suo ambito è assegnato (o usato come) a una variabile. Pertanto, la chiusura del nome: l'ambito e la funzione sono racchiusi e utilizzati proprio come qualsiasi altra entità.

Spiegazione approfondita dello stile di Wikipedia

Secondo Wikipedia, una chiusura è:

Tecniche per l'implementazione dell'associazione di nomi con ambito lessicale in linguaggi con funzioni di prima classe.

Cosa significa? Diamo un'occhiata ad alcune definizioni.

Spiegherò chiusure e altre definizioni correlate usando questo esempio:

function startAt(x) {
    return function (y) {
        return x + y;
    }
}

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

Funzioni di prima classe

Fondamentalmente ciò significa che possiamo usare funzioni come qualsiasi altra entità . Possiamo modificarli, passarli come argomenti, restituirli da funzioni o assegnarli a variabili. Tecnicamente parlando, sono cittadini di prima classe , da cui il nome: funzioni di prima classe.

Nell'esempio sopra, startAtrestituisce una funzione ( anonima ) a cui la funzione viene assegnata closure1e closure2. Quindi, come vedi, i trattamenti JavaScript funzionano esattamente come qualsiasi altra entità (cittadini di prima classe).

Rilegatura del nome

Vincolante Il è di scoprire quali dati una variabile (identificatore) i riferimenti . L'ambito è davvero importante qui, in quanto è ciò che determinerà come viene risolta un'associazione.

Nell'esempio sopra:

  • Nell'ambito della funzione anonima interna, yè vincolato a 3.
  • Nel startAtcampo di applicazione, xè associato 1o 5(a seconda della chiusura).

All'interno dell'ambito della funzione anonima, xnon è vincolato a nessun valore, quindi deve essere risolto in un ambito superiore startAt.

Scoping lessicale

Come dice Wikipedia , l'ambito:

È la regione di un programma per computer in cui è valida l'associazione: in cui è possibile utilizzare il nome per fare riferimento all'entità .

Esistono due tecniche:

  • Scoping lessico (statico): la definizione di una variabile viene risolta cercando il blocco o la funzione che lo contiene, quindi se la ricerca non riesce nel blocco contenitore esterno e così via.
  • Scoping dinamico: viene cercata la funzione di chiamata, quindi la funzione che ha chiamato quella funzione di chiamata e così via, facendo avanzare lo stack di chiamate.

Per ulteriori spiegazioni, dai un'occhiata a questa domanda e dai un'occhiata a Wikipedia .

Nell'esempio sopra, possiamo vedere che JavaScript ha un ambito lessicale, perché quando xviene risolto, l'associazione viene cercata nell'ambito superiore ( startAts), in base al codice sorgente (la funzione anonima che cerca x è definita all'interno startAt) e non basato sullo stack di chiamate, il modo (l'ambito in cui) è stata chiamata la funzione.

Avvolgimento (chiusura) in alto

Nel nostro esempio, quando chiamiamo startAt, verrà restituita una funzione (di prima classe) che verrà assegnata closure1e closure2quindi verrà creata una chiusura, perché le variabili passate 1e 5verranno salvate nell'ambito startAtdell'ambito, che verrà racchiuso con il valore restituito funzione anonima. Quando chiamiamo questa funzione anonima tramite closure1e closure2con lo stesso argomento ( 3), il valore di yverrà trovato immediatamente (poiché quello è il parametro di quella funzione), ma xnon è limitato nell'ambito della funzione anonima, quindi la risoluzione continua in l'ambito della funzione (lessicamente) superiore (che è stato salvato nella chiusura) dove xsi trova che è legato a uno 1o5. Ora sappiamo tutto per la somma in modo che il risultato possa essere restituito, quindi stampato.

Ora dovresti capire chiusure e come si comportano, che è una parte fondamentale di JavaScript.

accattivarsi

Oh, e hai anche imparato di cosa tratta il curry : usi le funzioni (chiusure) per passare ogni argomento di un'operazione invece di usare una funzione con più parametri.


5

La chiusura è una funzionalità di JavaScript in cui una funzione ha accesso alle proprie variabili di ambito, accesso alle variabili di funzione esterne e accesso alle variabili globali.

La chiusura ha accesso al suo ambito di funzione esterno anche dopo il ritorno della funzione esterna. Ciò significa che una chiusura può ricordare e accedere a variabili e argomenti della sua funzione esterna anche dopo che la funzione è terminata.

La funzione interna può accedere alle variabili definite nel proprio ambito, nell'ambito della funzione esterna e ambito globale. E la funzione esterna può accedere alla variabile definita nel proprio ambito e nell'ambito globale.

Esempio di chiusura :

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

L'output sarà 20 quale somma della propria variabile di funzione interna, variabile di funzione esterna e valore della variabile globale.


4

In una situazione normale, le variabili sono vincolate dalla regola di scoping: le variabili locali funzionano solo all'interno della funzione definita. La chiusura è un modo per infrangere temporaneamente questa regola per comodità.

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

nel codice sopra, lambda(|n| a_thing * n}è la chiusura perché a_thingviene indicata da lambda (un creatore di funzioni anonime).

Ora, se si inserisce la funzione anonima risultante in una variabile di funzione.

foo = n_times(4)

foo infrange la normale regola di scoping e inizia a usarne 4 internamente.

foo.call(3)

restituisce 12.


2

In breve, il puntatore a funzione è solo un puntatore a una posizione nella base di codice del programma (come il contatore del programma). Considerando Chiusura = Puntatore a funzione + Stack frame .

.


1

• Una chiusura è un sottoprogramma e l'ambiente di riferimento in cui è stato definito

- L'ambiente di riferimento è necessario se il sottoprogramma può essere richiamato da qualsiasi posizione arbitraria nel programma

- Un linguaggio con ambito statico che non consente sottoprogrammi nidificati non necessita di chiusure

- Le chiusure sono necessarie solo se un sottoprogramma può accedere alle variabili negli ambiti di annidamento e può essere chiamato da qualsiasi luogo

- Per supportare le chiusure, un'implementazione potrebbe dover fornire un'estensione illimitata ad alcune variabili (poiché un sottoprogramma può accedere a una variabile non locale che normalmente non è più attiva)

Esempio

function makeAdder(x) {
return function(y) {return x + y;}
}
var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write(″add 10 to 20: ″ + add10(20) +
″<br />″);
document.write(″add 5 to 20: ″ + add5(20) +
″<br />″);

0

Ecco un altro esempio di vita reale e l'utilizzo di un linguaggio di scripting popolare nei giochi: Lua. Avevo bisogno di cambiare leggermente il modo in cui una funzione di libreria funzionava per evitare un problema con stdin non disponibile.

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

Il valore di old_dofile scompare quando questo blocco di codice termina il suo ambito (perché è locale), tuttavia il valore è stato racchiuso in una chiusura, quindi la nuova funzione di dofile ridefinita PUO 'accedervi, o meglio una copia memorizzata insieme alla funzione come un 'sopravvalutare'.


0

Da Lua.org :

Quando una funzione è scritta racchiusa in un'altra funzione, ha pieno accesso alle variabili locali dalla funzione che racchiude; questa funzione si chiama scoping lessicale. Anche se può sembrare ovvio, non lo è. L'ambito lessicale, oltre a funzioni di prima classe, è un concetto potente in un linguaggio di programmazione, ma pochi linguaggi supportano tale concetto.


0

Se vieni dal mondo Java, puoi confrontare una chiusura con una funzione membro di una classe. Guarda questo esempio

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

La funzione gè una chiusura: si gchiude a. Quindi gpuò essere confrontata con una funzione membro, apuò essere confrontata con un campo di classe e la funzione fcon una classe.


0

Chiusure Ogni volta che abbiamo una funzione definita all'interno di un'altra funzione, la funzione interna ha accesso alle variabili dichiarate nella funzione esterna. Le chiusure sono meglio spiegate con esempi. Nel Listato 2-18, puoi vedere che la funzione interna ha accesso a una variabile (variabileInOuterFunction) dall'ambito esterno. Le variabili nella funzione esterna sono state chiuse (o associate) dalla funzione interna. Da qui il termine chiusura. Il concetto in sé è abbastanza semplice e abbastanza intuitivo.

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

fonte: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf


0

Dai un'occhiata sotto il codice per capire la chiusura in modo più approfondito:

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

Qui cosa verrà prodotto? 0,1,2,3,4non sarà 5,5,5,5,5per via della chiusura

Quindi come risolverà? La risposta è sotto:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

Consentitemi di spiegare semplicemente, quando una funzione creata non accade nulla fino a quando non viene chiamato così per il ciclo nel 1 ° codice chiamato 5 volte ma non chiamato immediatamente così quando viene chiamato cioè dopo 1 secondo e anche questo è asincrono, quindi prima che questo per il ciclo terminato e memorizzare il valore 5 in var ie infine esegui la setTimeoutfunzione cinque volte e stampa5,5,5,5,5

Ecco come si risolve usando IIFE, ovvero l'espressione della funzione di invocazione immediata

       (function(j){  //i is passed here           
            setTimeout(function(){
                           console.log(j);
                       },1000);
        })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

Per di più, si prega di comprendere il contesto di esecuzione per comprendere la chiusura.

  • Esiste un'altra soluzione per risolverlo usando let (funzione ES6) ma sotto la cappa sopra la funzione è funzionata

     for(let i=0; i< 5; i++){           
         setTimeout(function(){
                        console.log(i);
                    },1000);                        
     }
    
    Output: 0,1,2,3,4
    

=> Altre spiegazioni:

In memoria, quando per il ciclo eseguire l'immagine, fare come di seguito:

Loop 1)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Loop 2)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Loop 3)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Loop 4)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Loop 5)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Qui non sono eseguito e quindi dopo il ciclo completo, var ho archiviato il valore 5 in memoria ma il suo ambito è sempre visibile nella sua funzione figlio, quindi quando la funzione viene eseguita al rovescio setTimeoutper cinque volte, viene stampata5,5,5,5,5

quindi per risolvere questo uso IIFE come spiegato sopra.


grazie per la tua risposta. sarebbe più leggibile se separassi il codice dalla spiegazione. (non indentare le righe che non sono in codice)
eMBee,

0

Currying: ti permette di valutare parzialmente una funzione passando solo in un sottoinsieme dei suoi argomenti. Considera questo:

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

Chiusura: una chiusura non è altro che l'accesso a una variabile al di fuori dell'ambito di una funzione. È importante ricordare che una funzione all'interno di una funzione o di una funzione nidificata non è una chiusura. Le chiusure vengono sempre utilizzate quando è necessario accedere alle variabili al di fuori dell'ambito della funzione.

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21

0

La chiusura è molto semplice. Possiamo considerarlo come segue: Closure = function + il suo ambiente lessicale

Considera la seguente funzione:

function init() {
    var name = “Mozilla”;
}

Quale sarà la chiusura nel caso sopra? Funzione init () e variabili nel suo ambiente lessicale, ad esempio nome. Chiusura = init () + nome

Considera un'altra funzione:

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

Quali saranno le chiusure qui? La funzione interna può accedere alle variabili della funzione esterna. displayName () può accedere al nome della variabile dichiarato nella funzione parent, init (). Tuttavia, se presenti, verranno utilizzate le stesse variabili locali in displayName ().

Chiusura 1: funzione init + (variabile nome + funzione displayName ()) -> ambito lessicale

Chiusura 2: funzione displayName + (variabile nome) -> ambito lessicale


0

Le chiusure forniscono JavaScript con stato.

Programmare significa semplicemente ricordare le cose.

Esempio

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

Nel caso precedente, lo stato è memorizzato nella variabile "a". Seguiamo aggiungendo 1 a "a" più volte. Possiamo farlo solo perché siamo in grado di "ricordare" il valore. Il detentore dello stato, "a", mantiene quel valore in memoria.

Spesso, nei linguaggi di programmazione, si desidera tenere traccia delle cose, ricordare le informazioni e accedervi in ​​un secondo momento.

Questo, in altre lingue , è comunemente realizzato attraverso l'uso di classi. Una classe, proprio come le variabili, tiene traccia del suo stato. E le istanze di quella classe, a loro volta, hanno anche uno stato al loro interno. Stato significa semplicemente informazioni che è possibile archiviare e recuperare in un secondo momento.

Esempio

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

Come possiamo accedere a "peso" dal metodo "render"? Bene, grazie allo stato. Ogni istanza della classe Bread può rendere il proprio peso leggendolo dallo "stato", un luogo in memoria in cui è possibile memorizzare tali informazioni.

Ora, JavaScript è un linguaggio molto singolare che storicamente non ha classi (ora lo fa, ma sotto il cofano ci sono solo funzioni e variabili), quindi le Chiusure forniscono a JavaScript un modo per ricordare le cose e accedervi in ​​un secondo momento.

Esempio

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

L'esempio sopra ha raggiunto l'obiettivo di "mantenere lo stato" con una variabile. Questo è fantastico! Tuttavia, questo ha lo svantaggio che la variabile (il "titolare" dello stato) è ora esposta. Possiamo fare di meglio. Possiamo usare le chiusure.

Esempio

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

È fantastico.

Ora la nostra funzione "conta" può contare. È in grado di farlo solo perché può "trattenere" lo stato. Lo stato in questo caso è la variabile "n". Questa variabile è ora chiusa. Chiuso nel tempo e nello spazio. Nel tempo perché non sarai mai in grado di recuperarlo, modificarlo, assegnargli un valore o interagire direttamente con esso. Nello spazio perché è nidificato geograficamente nella funzione "countGenerator".

Perché è fantastico? Perché senza coinvolgere altri strumenti sofisticati e complicati (ad es. Classi, metodi, istanze, ecc.) Siamo in grado di 1. nascondere 2. il controllo a distanza

Nascondiamo lo stato, la variabile "n", che la rende una variabile privata! Abbiamo anche creato un'API che può controllare questa variabile in un modo predefinito. In particolare, possiamo chiamare l'API in questo modo "count ()" e questo aggiunge 1 a "n" da una "distanza". In nessun modo, forma o forma chiunque potrà mai accedere a "n" se non attraverso l'API.

JavaScript è davvero sorprendente nella sua semplicità.

Le chiusure sono una parte importante del perché questo è.


0

Un semplice esempio in Groovy come riferimento:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
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.