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?
(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:
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.
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.
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 .
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.
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. ”
'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?
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");
}
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:
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.).