it-swarm.asia

Kapanış nedir?

Arada sırada "kapanışların" bahsedildiğini görüyorum ve aramaya çalıştım ama Wiki anladığım bir açıklama yapmıyor. Burada biri bana yardım edebilir mi?

157
gablin

(Feragatname: Bu temel bir açıklamadır; tanım gereği, biraz basitleştiriyorum)

Bir kapatmayı düşünmenin en basit yolu, yerel olarak diğer değişkenlere erişme özelliğine sahip bir değişken olarak saklanabilen işlev ("birinci sınıf işlev" olarak adlandırılır). oluşturulduğu kapsam.

Örnek (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();

Fonksiyonlar1 atandı document.onclick ve displayValOfBlack kapaklardır. Her ikisinin de black boole değişkenine başvurduğunu, ancak bu değişkenin fonksiyonun dışında atandığını görebilirsiniz. black, işlevin tanımlandığı kapsamda yerel olduğundan , bu değişkenin işaretçisi korunur.

Bunu bir HTML sayfasına koyarsanız:

  1. Siyaha dönüştürmek için tıklayın
  2. "True" ifadesini görmek için [enter] tuşuna basın
  3. Tekrar tıklayın, beyaza dönün
  4. "False" değerini görmek için [enter] tuşuna basın

Bu, her ikisinin de aynı black erişimine sahip olduğunu ve durumu depolamak için kullanılabileceğini gösterir. herhangi bir sarmalayıcı nesnesi olmadan.

setKeyPress çağrısı, herhangi bir değişken gibi bir işlevin nasıl aktarılabileceğini göstermektir. Kapatmada korunan scope hala işlevin tanımlandığı yerdir.

Kapaklar, özellikle JavaScript ve ActionScript'te genellikle olay işleyiciler olarak kullanılır. Kapakların iyi kullanılması, bir nesne sarıcı oluşturmak zorunda kalmadan değişkenleri olay işleyicilerine dolaylı olarak bağlamanıza yardımcı olur. Bununla birlikte, dikkatsiz kullanım bellek sızıntılarına yol açacaktır (örneğin, kullanılmamış ancak korunmuş bir olay işleyicinin, bellekteki büyük nesnelere, özellikle DOM nesnelerine tutunması, çöp toplamayı engellemesi gibi tek şey olduğu gibi).


1: Aslında, JavaScript'teki tüm işlevler kapanır.

141
Nicole

Kapatma temel olarak bir nesneye bakmanın sadece farklı bir yoludur. Nesne, kendisine bağlı bir veya daha fazla işlevi olan verilerdir. Kapatma, kendisine bağlı bir veya daha fazla değişkeni olan bir işlevdir. İkisi temelde aynı, en azından uygulama seviyesinde. Asıl fark nereden geldikleri.

Nesne yönelimli programlamada, bir nesne sınıfını üye değişkenlerini ve yöntemlerini (üye işlevleri) önceden tanımlayarak bildirirsiniz ve ardından o sınıfın örneklerini yaratırsınız. Her örnek, kurucu tarafından başlatılan üye verilerinin bir kopyasıyla birlikte gelir. Daha sonra nesne türünde bir değişkeniniz olur ve onu bir veri parçası olarak iletirsiniz, çünkü odak veri olarak doğası üzerindedir.

Öte yandan, bir nesnede nesne bir nesne sınıfı gibi önceden tanımlanmaz veya kodunuzdaki bir yapıcı çağrısı ile başlatılır. Bunun yerine, kapağı başka bir işlevin içindeki bir işlev olarak yazarsınız. Kapatma, dış işlevin yerel değişkenlerinden herhangi birine başvurabilir ve derleyici bunu algılar ve bu değişkenleri dış işlevin yığın alanından kapağın gizli nesne bildirimine taşır. Daha sonra bir kapatma türünde bir değişkeniniz olur ve temelde kaputun altında bir nesne olmasına rağmen, onu bir işlev referansı olarak iletirsiniz, çünkü odak bir işlev olarak doğası üzerindedir.

69
Mason Wheeler

Kapatma terimi, bir kod parçasının (blok, fonksiyon), kapalı (ör. bir değere bağlı) kod bloğunun tanımlandığı ortam tarafından.

Örnek olarak Scala fonksiyon tanımı:

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

Fonksiyon gövdesinde iki tamsayı değerini gösteren iki isim (değişken) v ve k vardır. v adı, addConstant işlevinin bir argümanı olarak bildirildiği için bağlıdır (işlev bildirimine bakarak v değerine bir değer verileceğini biliyoruz işlev çağrıldığında). k ismi addConstant fonksiyonu için ücretsizdir, çünkü fonksiyon k değerinin (ve nasıl) bağlı olduğu hakkında hiçbir ipucu içermez.

Aşağıdaki gibi bir çağrıyı değerlendirmek için:

val n = addConstant(10)

k değerine bir değer atamalıyız; bu, yalnızca k adı, addConstant öğesinin tanımlandığı bağlamda tanımlandığında gerçekleşebilir. Örneğin:

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

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

  values.map(addConstant)
}

Şimdi addConstant ifadesini k tanımlı bir bağlamda tanımladığımıza göre, addConstant bir kapatma haline gelmiştir. artık tüm serbest değişkenleri kapalı (bir değere bağlı): addConstant bir işlevmiş gibi çağrılabilir ve aktarılabilir. k serbest değişkeninin, kapatma tanımlanmış olduğunda bir değere bağlı olduğunu, v bağımsız değişkeni ise kapatma çağrılır .

Dolayısıyla, kapanış temelde, bağlam tarafından bağlandıktan sonra serbest değişkenleri üzerinden yerel olmayan değerlere erişebilen bir işlev veya kod bloğudur.

Birçok dilde, bir kapağı yalnızca bir kez kullanırsanız, anonim yapabilirsiniz;.

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

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

Serbest değişkeni olmayan bir işlevin özel bir kapatma durumu olduğunu unutmayın (boş bir serbest değişkenler kümesiyle). Benzer şekilde, anonim işlev , anonim kapanışın özel bir halidir, yani anonim işlev, serbest değişken içermeyen anonim bir kapanıştır .

29
Giorgio

JavaScript'te basit bir açıklama:

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) önceden oluşturulan closure değerini kullanacaktır. Döndürülen alertValue işlevinin ad alanı, closure değişkeninin bulunduğu ad alanına bağlanır. Tüm işlevi sildiğinizde, closure değişkeninin değeri silinir, ancak o zamana kadar alertValue işlevi her zaman closure.

Bu kodu çalıştırırsanız, ilk yineleme closure değişkenine 0 değeri atar ve işlevi şu şekilde yeniden yazar:

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

Ve alertValue işlevi yürütmek için closure yerel değişkenine ihtiyaç duyduğundan, kendisini daha önce atanmış olan yerel değişken closure ile bağlar.

Ve şimdi closure_example İşlevini her çağırışınızda, alert(closure) bağlı olduğu için closure değişkeninin artan değerini yazacaktır.

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

Bir "kapanış" özünde, bir paket halinde birleştirilmiş bir yerel durum ve bir koddur. Tipik olarak, yerel durum çevreleyen (sözcüksel) bir kapsamdan gelir ve kod (esasen) daha sonra dışarıya döndürülen bir iç işlevdir. Bu durumda kapatma, iç fonksiyonun gördüğü yakalanan değişkenlerin ve iç fonksiyonun kodunun birleşimidir.

Bu, maalesef, bilmediğimiz için açıklanması biraz zor olan şeylerden biri.

Geçmişte başarılı bir şekilde kullandığım bir benzetme, "kitap" dediğimiz bir şeyin olduğunu hayal edin, oda kapanışında, "kitap", TAOCP'nin köşesinde, ama masa kapanmasında o kopya. , bu bir Dresden Files kitabının kopyası. Yani hangi kapanışta olduğunuza bağlı olarak, 'kitabı bana ver' kodu farklı şeyler oluyor. ”

5
Vatine

'Devlet' kavramını tanımlamaksızın kapatmanın ne olduğunu tanımlamak zordur.

Temel olarak, işlevleri birinci sınıf değerler olarak ele alan tam sözcük kapsamına sahip bir dilde, özel bir şey olur. Eğer şöyle bir şey yapsaydım:

function foo(x)
return x
end

x = foo

x değişkeni sadece function foo() 'e başvurmakla kalmaz, aynı zamanda son döndüğünde foo durumuna da başvurur. Gerçek sihir, foo, kapsamı içinde daha ayrıntılı tanımlanmış başka fonksiyonlara sahip olduğunda gerçekleşir; kendi mini ortamı gibi ('normal' gibi fonksiyonları global bir ortamda tanımladığımız gibi).

İşlevsel olarak, C++ (C?) 'In' statik 'anahtar kelimesiyle aynı sorunların çoğunu çözebilir; bu, yerel bir değişkenin durumunu birden fazla işlev çağrısı boyunca korur; ancak fonksiyonlar birinci sınıf değerler olduğu için bir fonksiyona aynı prensibi (statik değişken) uygulamak gibidir; closure, kaydedilecek tüm işlevin durumu için destek ekler (C++ 'ın statik işlevleriyle ilgisi yoktur).

İşlevleri birinci sınıf değerleri olarak ele almak ve kapaklar için destek eklemek, bellekte aynı sınıfın birden çok örneğine sahip olabileceğiniz anlamına gelir (sınıflara benzer). Bunun anlamı, bir fonksiyonun içindeki C++ statik değişkenleriyle uğraşırken gerektiği gibi fonksiyonun durumunu sıfırlamak zorunda kalmadan aynı kodu tekrar kullanabilmenizdir (bu konuda yanlış olabilir mi?).

İşte Lua'nın kapatma desteğinin bazı testleri.

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

sonuçlar:

nil
20
31
42

Zor olabilir ve muhtemelen dilden dile değişir, ancak Lua'da bir işlev yürütüldüğünde durumunun sıfırlandığı görülmektedir. myclosure işlevine/durumuna doğrudan erişirsek (döndürdüğü anonim işlev yerine), yukarıdaki koddaki sonuçlar farklı olacağından bunu söylüyorum, çünkü pvalue geri alınacak ila 10; ancak myclosure'un durumuna x (anonim işlev) aracılığıyla erişirsek, pvalue öğesinin canlı ve bellekte bir yerde olduğunu görebilirsiniz. Biraz daha fazla olduğundan şüpheleniyorum, belki biri uygulamanın doğasını daha iyi açıklayabilir.

Not: C++ 11 (önceki sürümlerde ne dışında) bir yalamak bilmiyorum, bu yüzden bu C++ 11 ve Lua kapanışları arasında bir karşılaştırma olmadığını unutmayın. Ayrıca, Lua'dan C++ 'a çizilen tüm' çizgiler 'statik değişkenlerle benzerlik göstermektedir ve kapanışlar% 100 aynı değildir; bazen benzer sorunları çözmek için kullanılıyor olsalar bile.

Emin değilim yukarıdaki kod örneğinde, anonim işlevi veya üst düzey işlevi kapatma olarak kabul edilir?

5
Trae Barlow

Kapatma, ilişkilendirilmiş durumu olan bir işlevdir:

Perl'de şöyle kapaklar oluşturursunuz:

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

C++ ile sağlanan yeni işlevselliğe bakarsak.
Ayrıca mevcut durumu nesneye bağlamanızı sağlar:

#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

Basit bir işlevi ele alalım:

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

Bu işleve üst düzey işlev denir, çünkü başka bir işlevin içinde yuvalanmaz. Her JavaScript işlevi kendisiyle "Kapsam Zinciri" adı verilen nesnelerin bir listesini ilişkilendirir . Bu kapsam zinciri, nesnelerin sıralı bir listesidir. Bu nesnelerin her biri bazı değişkenleri tanımlar.

Üst düzey işlevlerde, kapsam zinciri tek bir nesneden, global nesneden oluşur. Örneğin, yukarıdaki f1 işlevinde, içinde tüm global değişkenleri tanımlayan tek bir nesne bulunan bir kapsam zinciri vardır. (buradaki "nesne" teriminin JavaScript nesnesi anlamına gelmediğini, yalnızca, değişken bir kapsayıcı olarak işlev gören ve JavaScript'in değişkenleri "arayabildiği" bir uygulama tanımlı nesne olduğunu unutmayın.)

Bu işlev çağrıldığında, JavaScript "Aktivasyon nesnesi" olarak adlandırılan bir şey oluşturur ve onu kapsam zincirinin en üstüne koyar. Bu nesne tüm yerel değişkenleri içerir (örneğin x burada). Bu yüzden şimdi kapsam zincirinde iki nesnemiz var: birincisi aktivasyon nesnesidir ve altındaki küresel nesne.

İki nesnenin FARKLI zamanlarda kapsam zincirine yerleştirildiğine dikkat edin. Genel nesne işlev tanımlandığında (yani, JavaScript işlevi ayrıştırıp işlev nesnesini oluşturduğunda) konur ve işlev çağrıldığında etkinleştirme nesnesi girilir.

Şimdi bunu biliyoruz:

  • Her işlevin kendisiyle ilişkilendirilmiş bir kapsam zinciri vardır
  • İşlev tanımlandığında (işlev nesnesi oluşturulduğunda), JavaScript bu işlevle bir kapsam zinciri kaydeder
  • Üst düzey işlevler için, kapsam zinciri işlev tanımı zamanında yalnızca genel nesneyi içerir ve çağırma zamanında ek bir etkinleştirme nesnesi ekler

Yuvalanmış işlevlerle uğraşırken durum ilginçleşir. Şimdi bir tane oluşturalım:

function f1(x) {

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

}

f1 tanımlandığında, yalnızca global nesneyi içeren bir kapsam zinciri alırız.

Şimdi f1 çağrıldığında, f1 kapsam zinciri etkinleştirme nesnesini alır. Bu etkinleştirme nesnesi, x değişkenini ve bir işlev olan f2 değişkenini içerir. Ve f2 ifadesinin tanımlandığını unutmayın. Bu nedenle, JavaScript bu noktada f2 için yeni bir kapsam zinciri kaydeder. Bu iç işlev için kaydedilen kapsam zinciri geçerli olan geçerli kapsam zinciridir. Geçerli geçerli kapsam zinciri f1 's. Bu nedenle f2'nin kapsam zinciri f1 's geçerli kapsam zinciridir - f1 etkinleştirme nesnesini içerir ve küresel nesne.

f2 çağrıldığında, f1 etkinleştirme nesnesini ve global nesneyi içeren kapsam zincirine eklenen y içeren kendi etkinleştirme nesnesini alır.

f2 içinde tanımlı başka bir iç içe işlev olsaydı, kapsam zinciri tanım zamanında üç nesne (iki dış işlevin 2 etkinleştirme nesnesi ve genel nesne) ve çağırma zamanında 4 nesne içerecektir.

Şimdi, kapsam zincirinin nasıl çalıştığını anlıyoruz, ancak henüz kapanışlardan bahsetmedik.

Bir fonksiyon nesnesinin ve fonksiyonun değişkenlerinin çözüldüğü bir kapsamın (bir dizi değişken bağlamanın) kombinasyonuna bilgisayar bilimi literatüründe bir kapatma adı verilir - JavaScript, David Flanagan'ın kesin kılavuzu

Çoğu işlev, işlev tanımlandığında geçerli olan aynı kapsam zinciri kullanılarak çağrılır ve gerçekte bir kapatma olması önemli değildir. Kapaklar, tanımlandıklarında geçerli olandan farklı bir kapsam zinciri altında çağrıldığında ilginç hale gelir. Bu en çok, iç içe geçmiş bir işlev nesnesi, tanımlandığı işlevden döndürüldüğünde gerçekleşir.

İşlev döndüğünde, etkinleştirme nesnesi kapsam zincirinden kaldırılır. Yuvalanmış işlev yoksa, etkinleştirme nesnesine artık başvuru yoktur ve çöp toplanır. Yuvalanmış işlevler tanımlanmışsa, bu işlevlerin her birinin kapsam zincirine bir referansı vardır ve bu kapsam zinciri aktivasyon nesnesini ifade eder.

Eğer bu iç içe geçmiş işlevler nesneleri dış işlevlerinde kalırlarsa, kendileri atıfta bulundukları etkinleştirme nesnesiyle birlikte çöp toplanırlar. Ancak işlev iç içe bir işlev tanımlar ve işlevi bir yere döndürürse veya bir yerde bir özelliğe depolarsa, iç içe işleve dış bir başvuru olur. Çöp toplanmaz ve atıfta bulunduğu etkinleştirme nesnesi de çöp toplanmaz.

Yukarıdaki örneğimizde, f2 öğesinden f1 döndürmüyoruz, bu nedenle f1 çağrısı geri döndüğünde, etkinleştirme nesnesi kapsam zincirinden kaldırılır ve çöp toplanır. Ama böyle bir şeyimiz olsaydı:

function f1(x) {

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

    return f2;
}

Burada, dönen f2, f1 etkinleştirme nesnesini içerecek bir kapsam zincirine sahip olacaktır ve bu nedenle çöp toplanmayacaktır. Bu noktada, f2 adını verirsek, f1 dışında kalsak da f1 değişkenine x erişebilir.

Dolayısıyla, bir fonksiyonun kapsam zincirini onunla birlikte tuttuğunu ve kapsam zinciri ile dış fonksiyonların tüm aktivasyon nesnelerini geldiğini görebiliriz. Kapanmanın özü budur. JavaScript'teki işlevlerin "sözlüksel olarak kapsam dahilinde" olduğunu, yani, kapsamın aksine tanımlandıklarında etkin olan kapsamı kaydettiklerini söylüyoruz. arandıklarında aktif olurlar.

Özel değişkenlere yaklaşma, olay güdümlü programlama, kısmi uygulama , vb. Gibi kapanışları içeren bir dizi güçlü programlama tekniği vardır.

Ayrıca tüm bunların, kapanışları destekleyen tüm diller için geçerli olduğunu unutmayın. Örneğin PHP (5.3+), Python, Ruby, vb.).

2
treecoder