it-swarm.it

Cos'è una chiusura?

Di tanto in tanto vedo le "chiusure" menzionate, e ho provato a cercarlo, ma Wiki non dà una spiegazione che capisco. Qualcuno potrebbe aiutarmi qui?

157
gablin

(Dichiarazione di non responsabilità: questa è una spiegazione di base; per quanto riguarda la definizione, sto semplificando un po ')

Il modo più semplice di pensare a una chiusura è un funzione che può essere memorizzata come variabile (indicata come "funzione di prima classe"), che ha una capacità speciale di accedere ad altre variabili locali per l'ambito in cui è stato creato.

Esempio (JavaScript):

var setKeyPress = function(callback) {
    document.onkeypress = callback;
};

var initialize = function() {
    var black = false;

    document.onclick = function() {
        black = !black;
        document.body.style.backgroundColor = black ? "#000000" : "transparent";
    }

    var displayValOfBlack = function() {
        alert(black);
    }

    setKeyPress(displayValOfBlack);
};

initialize();

Le funzioni1 assegnato a document.onclick e displayValOfBlack sono chiusure. Potete vedere che entrambi fanno riferimento alla variabile booleana black, ma quella variabile è assegnata al di fuori della funzione. Poiché black è locale all'ambito in cui è stata definita la funzione , il puntatore a questa variabile viene conservato.

Se lo metti in una pagina HTML:

  1. Fai clic per passare al nero
  2. Premi [invio] per vedere "vero"
  3. Fai clic di nuovo, torna al bianco
  4. Premi [invio] per vedere "falso"

Ciò dimostra che entrambi hanno accesso allo stesso stesso black e possono essere utilizzati per memorizzare lo stato senza nessun oggetto wrapper.

La chiamata a setKeyPress è per dimostrare come una funzione può essere passata come qualsiasi variabile. scope conservato nella chiusura è ancora quello in cui è stata definita la funzione.

Le chiusure vengono comunemente utilizzate come gestori di eventi, soprattutto in JavaScript e ActionScript. Un buon uso delle chiusure ti aiuterà a associare implicitamente le variabili ai gestori di eventi senza dover creare un wrapper di oggetti. Tuttavia, un uso non attento porterà a perdite di memoria (come quando un gestore di eventi inutilizzato ma conservato è l'unica cosa da conservare su oggetti di grandi dimensioni in memoria, in particolare oggetti DOM, impedendo la garbage collection).


1: In realtà, tutte le funzioni in JavaScript sono chiusure.

141
Nicole

Una chiusura è fondamentalmente solo un modo diverso di guardare un oggetto. Un oggetto è un dato a cui sono associate una o più funzioni. Una chiusura è una funzione a cui sono associate una o più variabili. I due sono sostanzialmente identici, almeno a livello di implementazione. La vera differenza è da dove vengono.

Nella programmazione orientata agli oggetti, si dichiara una classe di oggetti definendo le sue variabili membro e i suoi metodi (funzioni membro) in anticipo, quindi si creano istanze di quella classe. Ogni istanza viene fornita con una copia dei dati dei membri, inizializzata dal costruttore. È quindi possibile disporre di una variabile di un tipo di oggetto e passarla come parte di dati, poiché l'attenzione è rivolta alla sua natura di dati.

In una chiusura, d'altra parte, l'oggetto non viene definito in anticipo come una classe di oggetti o istanziato attraverso una chiamata del costruttore nel codice. Invece, scrivi la chiusura come funzione all'interno di un'altra funzione. La chiusura può fare riferimento a una qualsiasi delle variabili locali della funzione esterna e il compilatore lo rileva e sposta queste variabili dallo spazio di stack della funzione esterna alla dichiarazione di oggetti nascosti della chiusura. Quindi hai una variabile di un tipo di chiusura e anche se è fondamentalmente un oggetto sotto il cofano, lo fai passare come riferimento di funzione, perché l'attenzione è sulla sua natura come funzione.

69
Mason Wheeler

Il termine chiusura deriva dal fatto che un pezzo di codice (blocco, funzione) può avere variabili libere che sono chiuso (cioè legato a un valore) dall'ambiente in cui il blocco di codice è definito.

Prendiamo ad esempio la Scala:

def addConstant(v: Int): Int = v + k

Nel corpo della funzione ci sono due nomi (variabili) v e k che indicano due valori interi. Il nome v è associato perché è dichiarato come argomento della funzione addConstant (osservando la dichiarazione della funzione sappiamo che a v verrà assegnato un valore quando la funzione viene invocato). Il nome k è gratuito rispetto alla funzione addConstant perché la funzione non contiene alcun indizio su quale valore k è associato (e come).

Per valutare una chiamata come:

val n = addConstant(10)

dobbiamo assegnare a k un valore, che può accadere solo se il nome k è definito nel contesto in cui è definito addConstant. Per esempio:

def increaseAll(values: List[Int]): List[Int] =
{
  val k = 2

  def addConstant(v: Int): Int = v + k

  values.map(addConstant)
}

Ora che abbiamo definito addConstant in un contesto in cui è definito k, addConstant è diventato un chiusura perché tutte le sue variabili libere sono ora - chiuso (associato a un valore): addConstant può essere invocato e passato come se fosse una funzione. Si noti che la variabile libera k è associata a un valore quando la chiusura è definita, mentre la variabile argomento v è associata quando la chiusura è invocata.

Quindi una chiusura è sostanzialmente una funzione o un blocco di codice che può accedere a valori non locali attraverso le sue variabili libere dopo che queste sono state vincolate dal contesto.

In molte lingue, se utilizzi una chiusura solo una volta puoi farlo anonimo, ad es.

def increaseAll(values: List[Int]): List[Int] =
{
  val k = 2

  values.map(v => v + k)
}

Si noti che una funzione senza variabili libere è un caso speciale di chiusura (con un set vuoto di variabili libere). Analogamente, un funzione anonima è un caso speciale di un chiusura anonima, ovvero una funzione anonima è una chiusura anonima senza variabili libere.

29
Giorgio

Una semplice spiegazione in JavaScript:

var closure_example = function() {
    var closure = 0;
    // after first iteration the value will not be erased from the memory
    // because it is bound with the returned alertValue function.
    return {
        alertValue : function() {
            closure++;
            alert(closure);
        }
    };
};
closure_example();

alert(closure) utilizzerà il valore precedentemente creato di closure. Lo spazio dei nomi della funzione alertValue restituito verrà collegato allo spazio dei nomi in cui risiede la variabile closure. Quando si elimina l'intera funzione, il valore della variabile closure verrà eliminato, ma fino ad allora, la funzione alertValue sarà sempre in grado di leggere/scrivere il valore della variabile closure.

Se si esegue questo codice, la prima iterazione assegnerà un valore 0 alla variabile closure e riscriverà la funzione a:

var closure_example = function(){
    alertValue : function(){
        closure++;
        alert(closure);
    }       
}

E poiché alertValue ha bisogno della variabile locale closure per eseguire la funzione, si lega con il valore della variabile locale precedentemente assegnata closure.

E ora ogni volta che chiami la funzione closure_example, Scriverà il valore incrementato della variabile closure perché alert(closure) è associato.

closure_example.alertValue()//alerts value 1 
closure_example.alertValue()//alerts value 2 
closure_example.alertValue()//alerts value 3
//etc. 
9
Muha

Una "chiusura" è essenzialmente uno stato locale e un po 'di codice, combinati in un pacchetto. In genere, lo stato locale proviene da un ambito circostante (lessicale) e il codice è (essenzialmente) una funzione interna che viene quindi restituita all'esterno. La chiusura è quindi una combinazione delle variabili catturate che vede la funzione interna e il codice della funzione interna.

È una di quelle cose che, sfortunatamente, è un po 'difficile da spiegare, a causa della mancanza di familiarità.

Un'analogia che ho usato con successo in passato era "immagina di avere qualcosa che chiamiamo" il libro ", nella chiusura della stanza," il libro "è quella copia lì, nell'angolo, di TAOCP, ma sulla chiusura del tavolo , è quella copia di un libro dei file di Dresda. Quindi, a seconda della chiusura in cui ti trovi, il codice "dammi il libro" provoca diverse cose. "

5
Vatine

È difficile definire cosa sia la chiusura senza definire il concetto di "stato".

Fondamentalmente, in un linguaggio con ambito lessicale completo che considera le funzioni come valori di prima classe, succede qualcosa di speciale. Se dovessi fare qualcosa del genere:

function foo(x)
return x
end

x = foo

La variabile x non solo fa riferimento a function foo() ma fa anche riferimento allo stato foo che è stato lasciato l'ultima volta che è tornato. La vera magia accade quando foo ha altre funzioni ulteriormente definite nel suo ambito; è come il suo mini-ambiente (così come "normalmente" definiamo le funzioni in un ambiente globale).

Funzionalmente può risolvere molti degli stessi problemi della parola chiave 'statica' del C++ (C?), Che mantiene lo stato di una variabile locale attraverso più chiamate di funzione; tuttavia è più come applicare lo stesso principio (variabile statica) a una funzione, poiché le funzioni sono valori di prima classe; la chiusura aggiunge il supporto per lo stato dell'intera funzione da salvare (nulla a che fare con le funzioni statiche di C++).

Considerare le funzioni come valori di prima classe e aggiungere il supporto per le chiusure significa anche che è possibile avere più di un'istanza della stessa funzione in memoria (simile alle classi). Ciò significa che è possibile riutilizzare lo stesso codice senza dover reimpostare lo stato della funzione, come è necessario quando si ha a che fare con variabili statiche C++ all'interno di una funzione (potrebbe esserci di sbagliato in questo?).

Ecco alcuni test del supporto di chiusura di Lua.

--Closure testing
--By Trae Barlow
--

function myclosure()
    print(pvalue)--nil
    local pvalue = pvalue or 10
    return function()
        pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed)
        print(pvalue)
        pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls)
        return pvalue
    end
end

x = myclosure() --x now references anonymous function inside myclosure()

x()--nil, 20
x() --21, 31
x() --32, 42
    --43, 53 -- if we iterated x() again

i risultati:

nil
20
31
42

Può diventare complicato, e probabilmente varia da lingua a lingua, ma in Lua sembra che ogni volta che viene eseguita una funzione, il suo stato viene ripristinato. Dico questo perché i risultati del codice sopra riportato sarebbero diversi se accedessimo direttamente alla funzione/stato myclosure (anziché tramite la funzione anonima che restituisce), poiché pvalue sarebbe ripristinato a 10; ma se accediamo allo stato di myclosure tramite x (la funzione anonima) puoi vedere che pvalue è vivo e vegeto da qualche parte nella memoria. Ho il sospetto che ci sia qualcosa in più, forse qualcuno può spiegare meglio la natura dell'implementazione.

PS: Non conosco una leccata di C++ 11 (diversa da quella delle versioni precedenti), quindi nota che questo non è un confronto tra le chiusure in C++ 11 e Lua. Inoltre, tutte le "linee tracciate" da Lua a C++ sono analogie in quanto le variabili statiche e le chiusure non sono uguali al 100%; anche se a volte vengono utilizzati per risolvere problemi simili.

La cosa di cui non sono sicuro è, nell'esempio di codice sopra, se la funzione anonima o la funzione di ordine superiore è considerata la chiusura?

5
Trae Barlow

Una chiusura è una funzione che ha stato associato:

In Perl crei chiusure come questa:

#!/usr/bin/Perl

# This function creates a closure.
sub getHelloPrint
{
    # Bind state for the function we are returning.
    my ($first) = @_;a

    # The function returned will have access to the variable $first
    return sub { my ($second) = @_; print  "$first $second\n"; };
}

my $hw = getHelloPrint("Hello");
my $gw = getHelloPrint("Goodby");

&$hw("World"); // Print Hello World
&$gw("World"); // PRint Goodby World

Se esaminiamo le nuove funzionalità fornite con C++.
Consente inoltre di associare lo stato corrente all'oggetto:

#include <string>
#include <iostream>
#include <functional>


std::function<void(std::string const&)> getLambda(std::string const& first)
{
    // Here we bind `first` to the function
    // The second parameter will be passed when we call the function
    return [first](std::string const& second) -> void
    {   std::cout << first << " " << second << "\n";
    };
}

int main(int argc, char* argv[])
{
    auto hw = getLambda("Hello");
    auto gw = getLambda("GoodBye");

    hw("World");
    gw("World");
}
4
Martin York

Consideriamo una semplice funzione:

function f1(x) {
    // ... something
}

Questa funzione è chiamata funzione di livello superiore perché non è nidificata in nessun'altra funzione. Ogni funzione JavaScript associa a sé un elenco di oggetti chiamato a "Scope Chain" . Questa catena di ambiti è un elenco ordinato di oggetti. Ognuno di questi oggetti definisce alcune variabili.

Nelle funzioni di livello superiore, la catena dell'ambito è costituita da un singolo oggetto, l'oggetto globale. Ad esempio, la funzione f1 Sopra ha una catena di ambito che contiene un singolo oggetto che definisce tutte le variabili globali. (nota che qui il termine "oggetto" non significa oggetto JavaScript, è solo un oggetto definito dall'implementazione che funge da contenitore di variabili, in cui JavaScript può "cercare" le variabili.)

Quando viene invocata questa funzione, JavaScript crea qualcosa chiamato "Activation object" e lo mette in cima alla catena dell'ambito. Questo oggetto contiene tutte le variabili locali (ad esempio x qui). Quindi ora abbiamo due oggetti nella catena dell'ambito: il primo è l'oggetto di attivazione e sotto di esso è l'oggetto globale.

Notare con molta attenzione che i due oggetti vengono inseriti nella catena dell'oscilloscopio in tempi DIVERSI. L'oggetto globale viene inserito quando viene definita la funzione (ad es. Quando JavaScript ha analizzato la funzione e creato l'oggetto funzione) e l'oggetto di attivazione entra quando viene invocata la funzione.

Quindi ora sappiamo questo:

  • Ad ogni funzione è associata una catena di portata
  • Quando viene definita la funzione (quando viene creato l'oggetto funzione), JavaScript salva una catena di ambito con quella funzione
  • Per le funzioni di livello superiore, la catena dell'ambito contiene solo l'oggetto globale al momento della definizione della funzione e aggiunge un oggetto di attivazione aggiuntivo in cima al momento della chiamata

La situazione diventa interessante quando abbiamo a che fare con funzioni nidificate. Quindi, creiamo uno:

function f1(x) {

    function f2(y) {
        // ... something
    }

}

Quando f1 Viene definito otteniamo una catena di ambito per esso contenente solo l'oggetto globale.

Ora quando viene chiamato f1, La catena di ambito di f1 Ottiene l'oggetto di attivazione. Questo oggetto di attivazione contiene la variabile x e la variabile f2 Che è una funzione. E nota che f2 Viene definito. Quindi, a questo punto, JavaScript salva anche una nuova catena di ambito per f2. La catena di portata salvata per questa funzione interna è la catena di portata corrente in vigore. La catena di portata corrente in effetti è quella di f1'S. Quindi la catena dell'ambito di f2 È la catena dell'ambito di f1 corrente - che contiene l'oggetto di attivazione di f1 E l'oggetto globale.

Quando viene chiamato f2, Ottiene il proprio oggetto di attivazione contenente y, aggiunto alla sua catena di portata che contiene già l'oggetto di attivazione di f1 E l'oggetto globale.

Se all'interno di f2 Fosse stata definita un'altra funzione nidificata, la catena dell'ambito conterrebbe tre oggetti al momento della definizione (2 oggetti di attivazione di due funzioni esterne e l'oggetto globale) e 4 al momento dell'invocazione.

Quindi, ora capiamo come funziona la catena dell'ambito ma non abbiamo ancora parlato di chiusure.

La combinazione di un oggetto funzione e un ambito (un insieme di associazioni di variabili) in cui vengono risolte le variabili della funzione è chiamata chiusura nella letteratura informatica - JavaScript la guida definitiva di David Flanagan

La maggior parte delle funzioni viene invocata utilizzando la stessa catena di ambito che era in vigore al momento della definizione della funzione e non importa che sia coinvolta una chiusura. Le chiusure diventano interessanti quando vengono invocate in una catena di ambito diversa da quella che era in vigore al momento della definizione. Ciò accade più comunemente quando un oggetto funzione nidificato viene restituito dalla funzione all'interno della quale è stato definito.

Quando la funzione ritorna, quell'oggetto di attivazione viene rimosso dalla catena dell'ambito. Se non c'erano funzioni nidificate, non ci sono più riferimenti all'oggetto di attivazione e viene raccolta spazzatura. Se sono state definite funzioni nidificate, ognuna di queste funzioni ha un riferimento alla catena dell'ambito e tale catena dell'ambito si riferisce all'oggetto di attivazione.

Se quegli oggetti con funzioni nidificate sono rimasti all'interno della loro funzione esterna, tuttavia, essi stessi verranno raccolti in modo inutile, insieme all'oggetto di attivazione a cui si riferivano. Ma se la funzione definisce una funzione nidificata e la restituisce o la memorizza in una proprietà da qualche parte, allora ci sarà un riferimento esterno alla funzione nidificata. Non sarà spazzatura raccolta e neanche l'oggetto di attivazione a cui si riferisce non sarà spazzatura.

Nel nostro esempio precedente, non restituiamo f2 Da f1, Quindi, quando viene restituita una chiamata a f1, Il suo oggetto di attivazione verrà rimosso dalla sua catena di portata e spazzatura raccolto. Ma se avessimo qualcosa del genere:

function f1(x) {

    function f2(y) {
        // ... something
    }

    return f2;
}

Qui, il ritorno f2 Avrà una catena di ambito che conterrà l'oggetto di attivazione di f1, E quindi non sarà raccolto in modo inutile. A questo punto, se chiamiamo f2, Sarà in grado di accedere alla variabile di f1x anche se siamo fuori da f1.

Quindi possiamo vedere che una funzione mantiene la sua catena di portata con essa e con la catena di portata arrivano tutti gli oggetti di attivazione delle funzioni esterne. Questa è l'essenza della chiusura. Diciamo che le funzioni in JavaScript sono "lessical scope" , il che significa che salvano l'ambito che era attivo quando sono state definite al contrario dell'ambito che era attivo quando sono state chiamate.

Esistono numerose potenti tecniche di programmazione che prevedono chiusure quali approssimazioni di variabili private, programmazione guidata da eventi, applicazione parziale , ecc.

Si noti inoltre che tutto ciò si applica a tutte quelle lingue che supportano le chiusure. Ad esempio PHP (5.3+), Python, Ruby, ecc.

2
treecoder