Då och då ser jag att ”stängningar” nämns, och jag försökte leta upp det men Wiki ger inte en förklaring som jag förstår. Kan någon hjälp mig här?
Kommentarer
- Om du vet Java / C # hoppas att den här länken hjälper- http://www.developerfusion.com/article/8251/the-beauty-of-closures/
- Stängningar är svåra att förstå. Du bör försöka klicka på alla länkarna i den första meningen i Wikipedia-artikeln och förstå dem artiklar först.
- stackoverflow.com/questions/36636/what-is-a-closure
- Vad ’ är den grundläggande skillnaden mellan en stängning och en klass? Okej, en klass med bara en offentlig metod.
- @biziclop: Du kunde emulera en stängning med en klass (att ’ är vad Java-devs måste göra). Men de ’ är vanligtvis något mindre detaljerade att skapa och du don t måste hantera manuellt vad du ’ ombitar. (Hardcore-lisperna ställer en liknande fråga, men kommer antagligen till den andra slutsatsen – att OO-stöd på språknivå är onödigt när du har stängningar).
Svar
(Ansvarsfriskrivning: detta är en grundförklaring; så långt definitionen går, förenklar jag lite)
Det enklaste sättet att tänka på en stängning är en -funktion som kan lagras som en variabel (kallas en ”första -klassfunktion ”), som har en speciell förmåga att komma åt andra variabler lokalt till det omfång det skapades i.
Exempel (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();
Funktionerna 1 tilldelade document.onclick
och displayValOfBlack
är nedläggningar. Du kan se att de båda hänvisar till den booleska variabeln black
, men den variabeln tilldelas utanför funktionen. Eftersom black
är lokal för det omfång där funktionen definierades , pekaren till denna variabel bevaras.
Om du lägger detta i en HTML-sida:
- Klicka för att ändra till svart
- Tryck [enter] för att se ”true”
- Klicka igen, ändras tillbaka till vit
- Tryck på [enter] för att se ”falskt”
Detta visar att båda har tillgång till samma black
, och kan användas för att lagra tillstånd utan något omslagningsobjekt.
Samtalet till setKeyPress
är för att visa hur en funktion kan skickas precis som alla variabler. scope som bevaras i förslutningen är fortfarande den där funktionen definierades.
Stängningar är vanligt används som händelsehanterare, särskilt i JavaScript och ActionScript. Bra användning av stängningar hjälper dig att implicit binda variabler till händelsehanterare utan att behöva skapa ett objektomslag. Oaktsam användning kommer emellertid att leda till minnesläckor (till exempel när en oanvänd men bevarad händelsehanterare är det enda man kan hålla fast i stora föremål i minnet, särskilt DOM-objekt, vilket förhindrar skräpsamling). > 1: Egentligen är alla funktioner i JavaScript stängda.
Kommentarer
- När jag läste ditt svar, jag kände en glödlampa tändas i mitt sinne. Mycket uppskattat! 🙂
- Eftersom
black
förklaras inuti en funktion skulle inte ’ t förstöras när stacken avlindas. ..? - @gablin, det är det som är unikt med språk som har stängningar. Alla språk med sopuppsamling fungerar ungefär på samma sätt – när inga fler referenser hålls till ett objekt kan det förstöras. När en funktion skapas i JS är det lokala omfånget bundet till den funktionen tills den funktionen förstörs.
- @gablin, det är ’ en bra fråga. Jag tror inte ’ att de kan ’ t & mdash; men jag tog bara upp skräpsamling eftersom det som JS använder och att ’ är vad du tycktes hänvisa till när du sa ” Sedan
black
deklareras i en funktion, skulle inte ’ t som förstörs ”. Kom också ihåg att om du deklarerar ett objekt i en funktion och sedan tilldelar det till en variabel som lever kvar någon annanstans, bevaras det objektet eftersom det finns andra referenser till det. - Objective-C (och C under clang) stöder block, som i huvudsak är förslutningar, utan sopuppsamling. Det kräver körtidsstöd och viss manuell intervention kring minneshantering.
Svar
En förslutning är i grunden bara ett annat sätt att titta på ett objekt. Ett objekt är data som har en eller flera funktioner bundna till sig. En stängning är en funktion som har en eller flera variabler bundna till sig. De två är i princip identiska, åtminstone på en implementeringsnivå. Den verkliga skillnaden är var de kommer ifrån.
I objektorienterad programmering deklarerar du en objektklass genom att definiera dess medlemsvariabler och dess metoder (medlemsfunktioner) framåt och sedan skapar du instanser av den klassen. Varje instans kommer med en kopia av medlemsdata, initierad av konstruktören. Du har sedan en variabel av en objekttyp och skickar den runt som en bit data, eftersom fokus ligger på dess natur som data.
I en förslutning, å andra sidan, är objektet inte definieras uppåt som en objektklass, eller instanseras genom ett konstruktörsanrop i din kod. Istället skriver du stängningen som en funktion inuti en annan funktion. Avslutningen kan hänvisa till vilken som helst av de yttre funktionens lokala variabler, och kompilatorn upptäcker det och flyttar dessa variabler från den yttre funktionens stackutrymme till förslutningsdeklarationen för dolda objekt. , och även om det i grunden är ett objekt under huven, skickar du det runt som en funktionsreferens, eftersom fokus ligger på dess natur som en funktion.
Kommentarer
- +1: Bra svar. Du kan se en stängning som ett objekt med endast en metod och ett godtyckligt objekt som en samling stängningar över några vanliga underliggande data (objektets ’ -variabler). Jag tycker att dessa två åsikter är ganska symmetriska.
- Mycket bra svar. Det förklarar faktiskt insikten om stängning.
- @Mason Wheeler: Var lagras stängningsdata? I stack som en funktion? Eller i hög som ett objekt?
- @RoboAlex: I högen, eftersom det ’ är ett objekt som ser ut som en funktion .
- @RoboAlex: Där en förslutning och dess fångade data lagras beror på implementeringen. I C ++ kan den lagras i högen eller i stacken.
Svar
Termen stängning kommer från det faktum att en kodkod (block, funktion) kan ha fria variabler som är stängd (dvs. bunden till ett värde) av den miljö där kodblocket definieras.
Ta till exempel definitionen av Scala-funktionen :
def addConstant(v: Int): Int = v + k
I funktionsdelen finns det två namn (variabler) v
och k
som anger två heltal. Namnet v
är bunden eftersom det deklareras som ett argument för funktionen addConstant
(genom att titta på funktionsdeklarationen vet vi att v
tilldelas ett värde när funktionen anropas). Namnet k
är fritt med funktionen addConstant
eftersom funktionen inte innehåller någon aning om vilket värde k
är bunden till (och hur).
För att utvärdera ett samtal som:
val n = addConstant(10)
måste vi tilldela k
ett värde, som bara kan hända om namnet k
definieras i det sammanhang där addConstant
definieras. Till exempel:
def increaseAll(values: List[Int]): List[Int] = { val k = 2 def addConstant(v: Int): Int = v + k values.map(addConstant) }
Nu när vi har definierat addConstant
i ett sammanhang där k
definieras, addConstant
har blivit en stängning eftersom alla dess fria variabler är nu stängda (bundna till ett värde): addConstant
kan åberopas och skickas runt som om det vore en funktion. Observera att den fria variabeln k
är bunden till ett värde när stängningen är definierad medan argumentvariabeln v
är bunden när stängningen är åberopas .
Så en stängning är i grunden en funktion eller kodblock som kan komma åt icke-lokala värden genom sina fria variabler efter att dessa har varit bundna av sammanhanget.
På många språk, om du använd en stängning bara när du kan göra det anonym , t.ex.
def increaseAll(values: List[Int]): List[Int] = { val k = 2 values.map(v => v + k) }
Observera att en funktion utan fria variabler är ett speciellt fall av en stängning (med en tom uppsättning fria variabler). Analogt är en anonym funktion ett speciellt fall av en anonym stängning , dvs en anonym funktion är en anonym stängning utan fria variabler.
Kommentarer
- Detta jibbar bra med stängda och öppna formler i logik. Tack för ditt svar.
- @RainDoctor: Gratisvariabler definieras i logiska formler och i lambda-beräkningsuttryck på liknande sätt: lambda i ett lambdauttryck fungerar som en kvantifierare i logiska formler med gratis / bundna variabler .
Svar
En enkel förklaring i 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)
använder det tidigare skapade värdet för closure
. Den returnerade alertValue
-funktionens namnrymd kommer att anslutas till namnområdet där variabeln closure
finns. När du tar bort hela funktionen, värdet på closure
-variabeln kommer att raderas, men tills dess kommer funktionen alertValue
alltid att kunna läsa / skriva värdet på variabeln closure
.
Om du kör den här koden tilldelas den första iteration ett värde 0 till closure
-variabeln och skriv om funktionen till:
var closure_example = function(){ alertValue : function(){ closure++; alert(closure); } }
Och eftersom alertValue
behöver den lokala variabeln closure
för att utföra funktionen binder den sig med värdet på den tidigare tilldelade lokala variabeln closure
.
Och nu varje gång du ringer till closure_example
-funktion, det skriver ut det ökade värdet för closure
-variabeln eftersom alert(closure)
är bunden.
closure_example.alertValue()//alerts value 1 closure_example.alertValue()//alerts value 2 closure_example.alertValue()//alerts value 3 //etc.
Kommentarer
- tack, jag gjorde inte ’ t testa koden =) allt verkar okej nu.
Svar
En ”stängning” är , i huvudsak, någon lokal stat och en del kod, kombinerat till ett paket. Vanligtvis kommer den lokala staten från ett omgivande (lexiskt) omfång och koden är (i huvudsak) en inre funktion som sedan återförs till utsidan. Avslutningen är då en kombination av de fångade variablerna som den inre funktionen ser och koden för den inre funktionen.
Det är en av de saker som tyvärr är lite svårt att förklara på grund av att vara okänd.
En analogi som jag framgångsrikt använde tidigare var ”föreställ oss att vi har något vi kallar” boken ”, i stängningen av rummet,” boken ”är den kopian där, i hörnet , av TAOCP, men i tabellstängningen är det den kopian av en Dresden Files-bok. Så beroende på vilken stängning du befinner dig i, så ger koden ”ge mig boken” olika saker. ”
Kommentarer
- Du har glömt detta: sv.wikipedia.org/wiki/Closure_(computer_programming) i ditt svar.
- Nej, jag valde konsekvent att inte stänga den sidan.
- ” Tillstånd och funktion. ”: Kan en C-funktion med en
static
lokal variabel betraktas som en stängning? i Haskell involverar tillstånd? - @Giorgio Stängningar i Haskell stänger (tror jag) över argumenten i det lexiska omfånget de ’ definieras i, så jag ’ säger ” ja ” (även om jag i bästa fall inte känner till Haskell). AC-funktion med en statisk variabel är i bästa fall en mycket begränsad stängning (du vill verkligen kunna skapa flera stängningar från en enda funktion, med en
static
lokal variabel har du exakt en). - Jag ställde den här frågan avsiktligt eftersom jag tror att en C-funktion med en statisk variabel inte är en slutning: den statiska variabeln definieras lokalt och bara känd inuti förslutningen, den kommer inte åt miljön. Jag är inte 100% säker men jag skulle formulera ditt uttalande tvärtom: du använder stängningsmekanismen för att skapa olika funktioner (en funktion är en stängningsdefinition + en bindning för dess fria variabler).
Svar
Det är svårt att definiera vad stängning är utan att definiera begreppet ”tillstånd”.
I grund och botten , på ett språk med full lexikalisk avgränsning som behandlar funktioner som förstklassiga värden, händer något speciellt. Om jag skulle göra något som:
function foo(x) return x end x = foo
Variabeln x
refererar inte bara till function foo()
men det refererar också till tillståndet foo
var kvar förra gången det återvände. Den verkliga magin händer när foo
har andra funktioner som definieras ytterligare inom sitt omfång; det är som sin egen minimiljö (precis som ”normalt” definierar vi funktioner i en global miljö).
Funktionellt kan den lösa många av samma problem som C ++ (C?) ”s” statiska ”nyckelord, som bibehåller en lokal variabels tillstånd under flera funktionsanrop; dock är det mer som att tillämpa samma princip (statisk variabel) på en funktion, eftersom funktioner är förstklassiga värden. stängning lägger till stöd för att hela funktionens tillstånd ska sparas (inget att göra med C ++ ”s statiska funktioner).
Att behandla funktioner som förstklassiga värden och lägga till stöd för stängningar innebär också att du kan ha mer än en instans av samma funktion i minnet (liknar klasser). Vad detta betyder är att du kan återanvända samma kod utan att behöva återställa funktionens tillstånd, vilket krävs när man hanterar C ++ statiska variabler i en funktion (kan vara fel om detta?).
Här är några tester av Luas stängningsstöd .
--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
resultat:
nil 20 31 42
Det kan bli svårt och det varierar förmodligen från språk till språk, men det verkar i Lua att när en funktion körs återställs dess tillstånd. Jag säger detta eftersom resultaten från koden ovan skulle vara annorlunda om vi hade tillgång till funktion / tillstånd direkt (istället för genom den anonyma funktionen som den returnerar), eftersom pvalue
skulle återställas till 10; men om vi kommer åt mitt stängningstillstånd genom x (den anonyma funktionen) kan du se att pvalue
lever och har det bra någonstans i minnet. Jag misstänker att det finns lite mer till det, kanske någon kan bättre förklara implementeringens karaktär.
PS: Jag vet inte en slick av C ++ 11 (annat än vad som finns i tidigare versioner) så observera att detta inte är en jämförelse mellan stängningar i C ++ 11 och Lua. Dessutom är alla ”linjer ritade” från Lua till C ++ likheter med statiska variabler och förslutningar är inte 100% desamma; även om de ibland används för att lösa liknande problem.
Det jag inte är säker på är, i kodexemplet ovan, om den anonyma funktionen eller den högre ordningsfunktionen anses vara stängningen?
Svar
En stängning är en funktion som har associerat tillstånd:
I perl skapar du stängningar så här:
#!/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
Om vi tittar på den nya funktionaliteten som tillhandahålls med C ++.
Det låter dig också binda nuvarande tillstånd till objektet:
#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"); }
Svar
Låt oss betrakta en enkel funktion:
function f1(x) { // ... something }
Denna funktion kallas en toppnivåfunktion eftersom den inte är kapslad i någon annan funktion. Alla JavaScript-funktionen associerar med sig en lista med objekt som kallas ” Scope Chain ”. en ordnad lista med objekt ach av dessa objekt definierar några variabler.
I toppnivåfunktioner består scope-kedjan av ett enda objekt, det globala objektet. Till exempel har funktionen f1
ovan en omfångskedja som har ett enda objekt som definierar alla globala variabler. (Observera att termen ”objekt” här inte betyder JavaScript-objekt, det är bara ett implementeringsdefinierat objekt som fungerar som en variabel behållare, där JavaScript kan ”slå upp” variabler.)
När detta funktionen åberopas, JavaScript skapar något som kallas ett ”Aktiveringsobjekt” och placerar det överst i omfångskedjan. objektet innehåller alla lokala variabler (till exempel x
här). Nu har vi därför två objekt i omfångskedjan: det första är aktiveringsobjektet och under det är det globala objektet.
Observera mycket noggrant att de två objekten placeras i omfångskedjan vid olika tidpunkter. Det globala objektet placeras när funktionen är definierad (dvs. när JavaScript analyseras och skapas funktionsobjektet) och aktiveringsobjekt träder in när funktionen anropas.
Så, vi vet nu detta:
- Varje funktion har en omfattningskedja associerad med sig
- Närfunktionen definieras (när funktionsobjektet skapas), JavaScript sparar en scope-kedja med den funktionen
- För toppnivåfunktioner innehåller scope-kedjan endast det globala objektet vid funktionsdefinitionstiden och lägger till ytterligare aktiveringsobjekt ovanpå anropstid
Situationen blir intressant när vi hanterar kapslade funktioner. Så, låt oss skapa en:
function f1(x) { function f2(y) { // ... something } }
När f1
definieras får vi en omfångskedja för den som innehåller bara det globala objektet.
Nu när f1
kallas, får räckviddskedjan för f1
aktiveringsobjektet. Detta aktiveringsobjekt innehåller variabeln x
och variabeln f2
som är en funktion. Och notera att f2
blir definierad.Därför sparar JavaScript nu en ny scope-kedja för f2
. Omfattningskedjan som sparats för denna inre funktion är den aktuella omfångskedjan i kraft. Det aktuella omfånget kedjan är i själva verket den för f1
”s. Därför är f2
” s omfångskedja f1
”s aktuell scope-kedja – som innehåller aktiveringsobjektet för f1
och det globala objektet.
När f2
kallas, får det ”s eget aktiveringsobjekt som innehåller y
, läggs till i sin omfångskedja som redan innehåller aktiveringsobjektet för f1
och det globala objektet.
Om det fanns en annan kapslad funktion definierad inom f2
, dess omfångskedja skulle innehålla tre objekt vid definitionstid (2 aktiveringsobjekt med två yttre funktioner och det globala objektet) och 4 vid anropstiden.
Så, nu undersöker vi tand hur scope-kedjan fungerar men vi har inte pratat om nedläggningar ännu.
Kombinationen av ett funktionsobjekt och ett scope (en uppsättning variabla bindningar) där funktionens variabler löses kallas en stängning i datavetenskapslitteraturen – JavaScript den definitiva guiden av David Flanagan
De flesta funktioner anropas med samma omfångskedja som var i kraft när funktionen definierades, och det spelar ingen roll att det är en nedläggning inblandad. Stängningar blir intressanta när de åberopas under en annan omfattningskedja än den som gällde när de definierades. Detta händer oftast när ett kapslat funktionsobjekt returneras från den funktion inom vilken det definierades.
När funktionen återgår tas det aktiveringsobjektet bort från omfångskedjan. Om det inte fanns några kapslade funktioner finns det inga fler referenser till aktiveringsobjektet och det samlas skräp. Om det definierades kapslade funktioner, har var och en av dessa funktioner en referens till omfångskedjan, och den omfångskedjan hänvisar till aktiveringsobjektet.
Om de kapslade funktionsobjekten förblev dock inom deras yttre funktion, då kommer de själva att samlas in skräp, tillsammans med aktiveringsobjektet de hänvisade till. Men om funktionen definierar en kapslad funktion och returnerar den eller lagrar den i en egendom någonstans, kommer det att finnas en extern referens till den kapslade funktionen. Det kommer inte att samlas in skräp, och aktiveringsobjektet som det refererar till kommer inte att samlas in skräp heller.
I vårt exempel ovan returnerar vi inte f2
från f1
, alltså, när ett samtal till f1
återgår, tas dess aktiveringsobjekt bort från dess omfångskedja och skräp samlas in. Men om vi hade något så här:
function f1(x) { function f2(y) { // ... something } return f2; }
Här kommer den återkommande f2
att ha en omfångskedja som innehåller aktiveringsobjektet för f1
, och därför kommer det inte att samlas in skräp. Vid denna punkt, om vi kallar f2
, kommer den att kunna komma åt f1
”s variabel x
även om vi ”är utanför f1
.
Därför kan vi se att en funktion håller sin omfångskedja med den och med omfångskedjan kommer alla aktiveringsobjekt för yttre funktioner. Detta är kärnan i stängningen. Vi säger att funktioner i JavaScript är ”lexiskt avgränsat” , vilket innebär att de sparar det omfång som var aktivt när de definierades i motsats till det omfång som var aktivt när de ringde.
Det finns ett antal kraftfulla programmeringstekniker som involverar stängningar som att approximera privata variabler , händelsestyrd programmering, partiell applikation , etc.
Observera också att allt detta gäller alla de språk som stöder stängning. PHP (5.3+), Python, Ruby, etc.
Svar
En förslutning är en kompilatoroptimering (aka syntaktiskt socker?). Vissa människor har också kallat detta Fattigmans objekt .
Se svaret av Eric Lippert : (utdrag nedan)
Kompilatorn genererar kod så här:
private class Locals { public int count; public void Anonymous() { this.count++; } } public Action Counter() { Locals locals = new Locals(); locals.count = 0; Action counter = new Action(locals.Anonymous); return counter; }
Vettigt?
Du bad också om jämförelser. VB och JScript skapar båda stängningar på ungefär samma sätt.
Kommentarer
- Detta svar är en CW eftersom jag inte ’ förtjänar poäng för Eric ’ s bra svar.Rösta upp det som du tycker passar. HTH
- -1: Din förklaring är för rot i C #. Stängning används på många språk och är mycket mer än syntaktiskt socker på dessa språk och omfattar både funktion och tillstånd.
- Nej, en stängning är inte bara en ” optimering av kompilator ” eller syntaktiskt socker. -1