it-swarm-eu.dev

Co je uzavření?

Tu a tam vidím zmíněné „uzávěry“ a snažil jsem se to vyhledat, ale Wiki nedává vysvětlení, kterému rozumím. Mohl by mi někdo pomoct tady?

157
gablin

(Zřeknutí se odpovědnosti: toto je základní vysvětlení; pokud jde o definici, trochu zjednodušuji)

Nejjednodušší způsob, jak přemýšlet o uzavření, je funkce, která může být uložena jako proměnná (označována jako "prvotřídní funkce"), která má speciální schopnost přístupu k dalším proměnným lokálním rozsah, ve kterém byl vytvořen.

Příklad (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();

Funkce1 přiřazen document.onclick a displayValOfBlack jsou uzávěry. Vidíte, že oba odkazují na booleovskou proměnnou black, ale tato proměnná je přiřazena mimo funkci. Protože black je místní pro rozsah, kde byla funkce definována , ukazatel na tuto proměnnou je zachován.

Pokud to vložíte na stránku HTML:

  1. Klepnutím změníte na černou
  2. Stisknutím klávesy [Enter] zobrazíte „true“
  3. Klikněte znovu, změní se zpět na bílou
  4. Stisknutím klávesy [Enter] zobrazíte „false“

To ukazuje, že oba mají přístup k stejnému black a lze je použít k uložení stavu bez jakéhokoli objektu obálky.

Volání setKeyPress je ukázat, jak lze funkci předat stejně jako jakékoli jiné proměnné. obor uchovaný v závěru je stále ten, kde byla funkce definována.

Uzávěry se běžně používají jako obsluhy událostí, zejména v jazyce JavaScript a ActionScript. Správné použití uzávěrů vám pomůže implicitně vázat proměnné na obslužné rutiny událostí, aniž byste museli vytvářet obálky objektů. Neopatrné použití však povede k úniku paměti (například když nepoužitý, ale zachovaný obslužný program události je jediná věc, kterou lze držet velkým objektům v paměti, zejména objektům DOM, což zabraňuje shromažďování odpadků).


1: Ve skutečnosti jsou všechny funkce v JavaScriptu uzavřením.

141
Nicole

Uzavření je v podstatě jen jiný způsob pohledu na objekt. Objektem jsou data, která k sobě mají jednu nebo více funkcí. Uzavření je funkce, ke které je vázána jedna nebo více proměnných. Oba jsou v podstatě totožné, přinejmenším na úrovni implementace. Skutečný rozdíl je v tom, odkud pocházejí.

V objektově orientovaném programování deklarujete třídu objektů definováním jejích členských proměnných a jejích metod (členské funkce) předem a poté vytvoříte instance této třídy. Každá instance přichází s kopií členských dat, inicializovaných konstruktorem. Potom máte proměnnou typu objektu a předáváte ji jako část dat, protože je kladen důraz na její povahu jako data.

Na závěr, na druhé straně, objekt není definován dopředu jako třída objektu, ani instancován pomocí volání konstruktoru v kódu. Místo toho píšete uzávěrku jako funkci uvnitř jiné funkce. Uzavření může odkazovat na jakoukoli místní proměnnou vnější funkce a kompilátor to detekuje a přesune tyto proměnné z prostoru zásobníku vnější funkce do deklarace skrytého objektu uzávěru. Pak máte proměnnou typu uzávěru, a přestože je to v podstatě objekt pod kapotou, předáváte ji jako funkční odkaz, protože se zaměřuje na její povahu jako funkci.

69
Mason Wheeler

Termín zavření vychází ze skutečnosti, že část kódu (blok, funkce) může mít volné proměnné, které jsou zavřené (tj. Vázané na hodnotu) prostředím, ve kterém blok kódu je definován.

Vezměme například definici funkce Scala):

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

V těle funkce jsou dvě jména (proměnné) v a k označující dvě celočíselné hodnoty. Jméno v je vázáno, protože je deklarováno jako argument funkce addConstant (při pohledu na deklaraci funkce víme, že v bude při funkci přiřazena hodnota je vyvolán). Jméno k je zdarma s funkcí addConstant, protože funkce neobsahuje ponětí, k jaké hodnotě k je vázána (a jak).

Chcete-li vyhodnotit hovor, například:

val n = addConstant(10)

musíme přiřadit k hodnotu, která se může stát, pouze pokud je jméno k definováno v kontextu, ve kterém je definována addConstant. Například:

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

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

  values.map(addConstant)
}

Nyní, když jsme definovali addConstant v kontextu, kde je definován k, addConstant se stalo zavření, protože všechny jeho volné proměnné jsou nyní - zavřeno (vázáno na hodnotu): addConstant lze vyvolat a předávat, jako by to byla funkce. Všimněte si, že volná proměnná k je vázána na hodnotu, když je uzavření definované, zatímco argumentová proměnná v je vázána, když je uzavření vyvoláno).

Uzavření je tedy v podstatě funkcí nebo kódovým blokem, který má přístup k nelokálním hodnotám prostřednictvím svých volných proměnných poté, co byly vázány kontextem.

V mnoha jazycích, pokud použijete uzávěrku pouze jednou, můžete ji provést anonymně, např.

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

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

Všimněte si, že funkce bez volných proměnných je zvláštním případem uzavření (s prázdnou sadou volných proměnných). Analogicky anonymní funkce je zvláštní případ anonymní uzavření, tj. Anonymní funkce je anonymní uzavření bez volných proměnných.

29
Giorgio

Jednoduché vysvětlení v JavaScriptu:

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) použije dříve vytvořenou hodnotu closure. Vrácený obor názvů funkce alertValue bude připojen k oboru názvů, ve kterém se nachází proměnná closure. Když odstraníte celou funkci, bude hodnota proměnné closure odstraněna, ale do té doby bude funkce alertValue vždy schopna číst/zapisovat hodnotu proměnné closure.

Pokud spustíte tento kód, první iterace přiřadí hodnotu 0 proměnné closure a přepíše funkci:

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

A protože alertValue potřebuje k provedení funkce místní proměnnou closure, sváže se s hodnotou dříve přiřazené místní proměnné closure.

A nyní pokaždé, když voláte funkci closure_example, Vypíše zvýšenou hodnotu proměnné closure, protože je vázána funkce alert(closure).

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

„Uzavření“ je v podstatě nějaký místní stát a nějaký kód, kombinovaný do balíčku. Obvykle místní stav pochází z okolního (lexikálního) rozsahu a kód je (v podstatě) vnitřní funkcí, která se potom vrací ven. Uzavření je pak kombinací zachycených proměnných, které vidí vnitřní funkce, a kódu vnitřní funkce.

Je to jedna z těch věcí, které je, bohužel, trochu obtížné vysvětlit, protože jsou neznámé.

Jedna analogie, kterou jsem v minulosti úspěšně použil, byla „představte si, že máme něco, čemu říkáme„ kniha “, v uzavření místnosti,„ kniha “je ta kopie tam, přes roh, TAOCP, ale na uzavření stolu , je to ta kopie knihy Dresden Files. Takže v závislosti na tom, v jakém závěru jste, kód 'dej mi knihu' vede k různým věcem. “

5
Vatine

Je těžké definovat, co je uzavření, aniž by definoval pojem „stát“.

V podstatě se v jazyce s plným lexikálním rozsahem, který považuje za hodnoty první třídy, stane něco zvláštního. Kdybych měl udělat něco jako:

function foo(x)
return x
end

x = foo

Proměnná x nejen odkazuje na function foo(), ale také odkazuje na stav foo, který byl ponechán při posledním návratu. Skutečná magie se stane, když foo má v rámci svého rozsahu další funkce; je to jako jeho vlastní mini prostředí (stejně jako „normálně“ definujeme funkce v globálním prostředí).

Funkčně může vyřešit mnoho stejných problémů jako klíčové slovo „statické“ klíčové slovo C++ (C?), Které zachovává stav lokální proměnné během více volání funkcí; je to však spíš aplikování stejného principu (statická proměnná) na funkci, protože funkce jsou hodnoty první třídy; Uzavření přidává podporu pro uložení celého stavu funkce (nic společného se statickými funkcemi C++).

Zpracování funkcí jako hodnot první třídy a přidání podpory pro uzavření také znamená, že v paměti můžete mít více než jednu instanci stejné funkce (podobně jako u tříd). Co to znamená, že můžete znovu použít stejný kód, aniž byste museli resetovat stav funkce, jak je vyžadováno při práci se statickými proměnnými C++ uvnitř funkce (může se v tom mýlit?).

Zde je několik testů podpory uzavření Luy.

--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

výsledek:

nil
20
31
42

Může to být složitější a pravděpodobně se liší od jazyka k jazyku, ale zdá se, že v Lua, že kdykoli je funkce vykonána, její stav je resetován. Říkám to proto, že výsledky z výše uvedeného kódu by se lišily, kdybychom přímo přistupovali k funkci/stavu myclosure (namísto prostřednictvím anonymní funkce, která se vrací), protože pvalue by bylo resetováno zpět do 10; ale pokud přistoupíme ke stavu myclosure přes x (anonymní funkce), můžete vidět, že pvalue je naživu a někde v paměti. Mám podezření, že je o něco víc, možná někdo může lépe vysvětlit povahu implementace.

PS: Neznám lízání C++ 11 (jiné než to, co je v předchozích verzích), takže si všimněte, že to není srovnání uzavírání v C++ 11 a Lua. Rovněž všechny „linie nakreslené“ od Lua do C++ jsou podobné jako statické proměnné a uzávěry nejsou 100% stejné; i když se někdy používají k řešení podobných problémů.

Věc, o které si nejsem jistá, je v příkladu kódu výše, zda je anonymní funkce nebo funkce vyššího řádu považována za uzavření?

5
Trae Barlow

Uzavření je funkce, která má přidružený stav:

V Perlu vytvoříte uzávěry takto:

#!/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

Podíváme-li se na nové funkce poskytované s C++.
Umožňuje také svázat aktuální stav k objektu:

#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

Uvažujme jednoduchou funkci:

_function f1(x) {
    // ... something
}
_

Tato funkce se nazývá funkce nejvyšší úrovně, protože není vnořena do žádné jiné funkce.KaždáJavaScript funkce spojuje s sebou seznam objektů nazvaných a"Řetězec rozsahu". Tento řetězec oboru je uspořádaný seznam objektů. Každý z těchto objektů definuje některé proměnné.

Ve funkcích nejvyšší úrovně se řetězec oboru skládá z jednoho objektu, globálního objektu. Například funkce _f1_ výše má řetězec oboru, který obsahuje jediný objekt, který definuje všechny globální proměnné. (Všimněte si, že termín "objekt" zde neznamená objekt JavaScript, je to pouze objekt definovaný implementací, který funguje jako kontejner proměnných, ve kterém JavaScript může vyhledávat proměnné.)

Při vyvolání této funkce vytvoří JavaScript něco, co se nazývá"Aktivační objekt", a umístí jej na začátek řetězce oboru. Tento objekt obsahuje všechny lokální proměnné (například x zde). Proto nyní máme v řetězci oboru dva objekty: první je aktivační objekt a pod ním je globální objekt.

Velmi pečlivě si povšimněte, že tyto dva objekty jsou umístěny v řetězci oboru v různém čase. Globální objekt se vloží, když je funkce definována (tj. Když JavaScript analyzoval funkci a vytvořil funkční objekt) a aktivační objekt vstoupí, když je funkce vyvolána.

Teď to víme:

  • Každá funkce má přidružený řetězec oborů
  • Když je funkce definována (když je vytvořen funkční objekt), JavaScript s touto funkcí uloží řetězec oboru
  • U funkcí nejvyšší úrovně obsahuje řetězec oborů pouze globální objekt v době definování funkce a přidává další aktivační objekt na vrcholu v okamžiku vyvolání

Situace se stává zajímavou, když se zabýváme vnořenými funkcemi. Vytvořme tedy jeden:

_function f1(x) {

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

}
_

Když je definována _f1_, dostaneme pro ni řetězec rozsahu obsahující pouze globální objekt.

Nyní, když se zavolá _f1_, získá řetězec rozsahu _f1_ aktivační objekt. Tento aktivační objekt obsahuje proměnnou x a proměnnou _f2_, což je funkce. A mějte na paměti, že _f2_ se definuje. V tomto okamžiku tedy JavaScript také uloží nový řetězec oborů pro _f2_.Řetězec rozsahu uložený pro tuto vnitřní funkci je platný aktuální řetězec oboru.Aktuální řetězec oboru je ve skutečnosti řetězec _f1_ 's. Řetězec _f2_ je tedy _f1_ 'saktuálnířetězec - který obsahuje aktivační objekt _f1_ a globální objekt.

Když se volá _f2_, získá vlastní aktivační objekt obsahující y, přidaný do řetězce řetězce, který již obsahuje aktivační objekt _f1_ a globální objekt.

Pokud by byla v rámci _f2_ definována další vnořená funkce, její řetězec působnosti by obsahoval tři objekty v definičním čase (2 aktivační objekty dvou vnějších funkcí a globální objekt) a 4 v době vyvolání.

Takže nyní chápeme, jak funguje řetězec rozsahu, ale ještě jsme nemluvili o uzavření.

Kombinace funkčního objektu a rozsahu (sady proměnných vazeb), ve kterém jsou proměnné funkce vyřešeny, se nazývá uzávěr v počítačové vědě - JavaScript definitivního průvodce Davida Flanagana

Většina funkcí je vyvolána pomocí stejného řetězce oborů, který platil při definování funkce, a nezáleží na tom, zda se jedná o uzavření. Uzávěry se stávají zajímavými, když jsou vyvolány v jiném řetězci rozsahu, než který byl platný, když byly definovány. K tomu nejčastěji dochází, když je vnořený funkční objektvrácenýz funkce, ve které byl definován.

Když se funkce vrátí, je tento aktivační objekt odstraněn z řetězce oboru. Pokud neexistovaly žádné vnořené funkce, neexistují žádné další odkazy na aktivační objekt a bude shromažďován odpad. Pokud byly definovány vnořené funkce, každá z těchto funkcí má odkaz na řetězec oboru a tento řetězec oboru odkazuje na aktivační objekt.

Pokud však tyto vnořené funkční objekty zůstanou v rámci své vnější funkce, budou samy o sobě shromážděny odpadky spolu s aktivačním objektem, na který se odkazují. Pokud ale funkce definuje vnořenou funkci a vrátí ji nebo ji někde uloží do vlastnosti, bude existovat externí odkaz na vnořenou funkci. Nebude shromažďováno odpadky a aktivační objekt, na který se odkazuje, nebude shromažďován ani odpadky.

V našem výše uvedeném příkladu nevracíme _f2_ z _f1_, takže když se vrátí volání na _f1_, bude jeho aktivační objekt odstraněn z řetězce oboru a shromážděné odpadky. Ale pokud bychom měli něco takového:

_function f1(x) {

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

    return f2;
}
_

Vracející se _f2_ bude mít řetězec oboru, který bude obsahovat aktivační objekt _f1_, a nebude tedy shromažďován odpadky. V tomto okamžiku, pokud voláme _f2_, bude mít přístup k _f1_ proměnné x, i když jsme mimo _f1_.

Můžeme tedy vidět, že funkce udržuje řetězec rozsahu s ním a s řetězcem oboru přicházejí všechny aktivační objekty vnějších funkcí. To je podstata uzavření. Říkáme, že funkce v JavaScriptu jsou"lexically scoped", což znamená, že ukládají rozsah, který byl aktivní, když byly definovány, na rozdíl od rozsahu, který byl aktivní, když dostali volala.

Existuje řada výkonných programovacích technik, které zahrnují uzavření, jako je aproximace soukromých proměnných, události řízené programování, částečná aplikace atd.

Všimněte si také, že to vše platí pro všechny jazyky, které podporují uzavření. Například PHP (5.3+), Python, Ruby atd.).

2
treecoder