Her şimdi ve sonra “kapanışlar” denildiğini görüyorum ve aramaya çalıştım ama Wiki anladığım bir açıklama yapmıyor. Biri bana yardım edebilir mi?
Her şimdi ve sonra “kapanışlar” denildiğini görüyorum ve aramaya çalıştım ama Wiki anladığım bir açıklama yapmıyor. Biri bana yardım edebilir mi?
Yanıtlar:
(Feragatname: bu temel bir açıklamadır; tanımın ilerleyişine göre, biraz basitleştiriyorum)
Bir kapatmayı düşünmenin en basit yolu , bir değişken olarak saklanabilen ("birinci sınıf bir işlev" olarak adlandırılır), oluşturulduğu kapsamda yerel olarak diğer değişkenlere erişme özelliğine sahip bir işlevdir.
Ö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();
Fonksiyonlar 1 atanır document.onclick
ve displayValOfBlack
kapanır. Her ikisinin de boole değişkenine başvurduğunu görebilirsiniz black
, ancak bu değişkene fonksiyonun dışında atanmış. İşlevin tanımlandığı kapsamda yerel olduğundan black
, bu değişkene işaretçi korunur.
Bunu bir HTML sayfasına koyarsanız:
Bu, her ikisinin de aynı erişime sahip olduğunu black
ve herhangi bir sarmalayıcı nesnesi olmadan durumu depolamak için kullanılabileceğini göstermektedir .
Çağrı, setKeyPress
bir işlevin herhangi bir değişken gibi nasıl iletilebileceğini göstermektir. Kapsamı kapatma korunmuş hala fonksiyon tanımlandı biridir.
Kapaklar, genellikle JavaScript ve ActionScript'te olay işleyicileri olarak kullanılır. Kapakların iyi kullanılması, bir nesne sarmalayıcı oluşturmak zorunda kalmadan değişkenleri olay işleyicilere dolaylı olarak bağlamanıza yardımcı olur. Bununla birlikte, dikkatsiz kullanım bellek sızıntılarına neden olacaktır (örneğin kullanılmayan ancak korunan bir olay işleyicisinin bellekteki büyük nesnelere, özellikle de DOM nesnelerini, çöp toplanmasını önleyen tek şey olduğu zaman).
1: Aslında, JavaScript'teki tüm işlevler kapanıyor.
black
bir işlev içinde ilan edilir yığın açıldıkça, bu ... yok olsun ki?
black
Bir işlevin içinde bildirildiği için, bu imha edilmeyecekti ” derken neyi kastediyordunuz . Ayrıca, bir işlevdeki bir nesneyi bildirir ve sonra onu başka bir yerde yaşayan bir değişkene atarsanız, o nesnenin korunduğu için başka referanslar olduğunu unutmayın.
Bir kapatma, temel olarak bir nesneye bakmanın sadece farklı bir yoludur. Bir nesne, kendisine bağlı bir veya daha fazla işlevi olan veridir. Kapatma, kendisine bağlı bir veya daha fazla değişken içeren bir fonksiyondur. İkisi en azından bir uygulama düzeyinde temelde aynıdır. Asıl fark, nereden geldikleridir.
Nesne yönelimli programlamada, bir nesne sınıfını, üye değişkenlerini ve yöntemlerini (üye işlevleri) önceden tanımlayarak tanımlarsınız ve sonra o sınıfın örneklerini yaratırsınız. Her örnek, yapıcı tarafından ilklendirilen üye verilerinin bir kopyası ile birlikte gelir. Daha sonra, bir nesne türünde bir değişkene sahip olursunuz ve bunu bir veri parçası olarak geçirirsiniz, çünkü odak, veri olarak doğası üzerinedir.
Öte yandan, kapanışta, nesne bir nesne sınıfı gibi ön tarafta tanımlanmaz veya kodunuzdaki bir kurucu çağrı ile başlatılır. Bunun yerine, kapağı başka bir işlevin içine bir işlev olarak yazarsınız. Kapak, dış fonksiyonun herhangi bir yerel değişkenine atıfta bulunabilir ve derleyici bu algılayıcıları tespit eder ve bu değişkenleri dış fonksiyonun yığın alanından kapağın gizli nesne bildirimine taşır. Daha sonra bir kapatma tipi değişkenine sahipsiniz ve temel olarak kaputun altındaki bir nesne olmasına rağmen, bir fonksiyon referansı olarak etrafa geçirin çünkü odak bir fonksiyon olarak doğası üzerinde.
Kapatma terimi , bir kod parçasının (blok, işlev) , kod bloğunun tanımlandığı ortam tarafından kapatılan (yani bir değere bağlı ) serbest değişkenlere sahip olmasından kaynaklanmaktadır .
Örneğin, Scala işlevi tanımını ele alalım:
def addConstant(v: Int): Int = v + k
İşlev gövdesinde iki ad vardır (değişkenler) v
ve k
iki tam sayı değerini belirtir. Ad v
, fonksiyonun argümanı olarak bildirildiği için sınırlıdır addConstant
(işlev bildirgesine bakarak v
, işlev çağrıldığında bir değere atanacağını biliyoruz ). Ad k
, fonksiyon ile serbesttir addConstant
çünkü fonksiyon hangi değere k
bağlı olduğuna dair hiçbir ipucu içermez (ve nasıl).
Bir aramayı şöyle değerlendirmek için:
val n = addConstant(10)
k
sadece k
tanımlandığı bağlamda tanımlanmışsa gerçekleşebilecek bir değer atamak zorundayız addConstant
. Örneğin:
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
def addConstant(v: Int): Int = v + k
values.map(addConstant)
}
Şimdi tanımlanmış olması addConstant
bağlamında k
tanımlanan, addConstant
bir haline gelmiştir kapatma bütün serbest değişkenler artık çünkü kapalı : (a değere bağlı) addConstant
çağrılabilir ve bir işlev gibi etrafında geçti. Serbest değişkenin k
, kapak tanımlandığında bir değere bağlı olduğuna v
, kapama çağrıldığında argüman değişkeninin bağlı olduğuna dikkat edin .
Bu nedenle, kapatma, temel olarak, yerel olarak bulunmayan değerlere, içerik tarafından bağlandıktan sonra kendi serbest değişkenleri yoluyla erişebilen bir işlev veya kod bloğudur.
Birçok dilde, yalnızca bir kez kapatma kullanıyorsanız , isimsiz yapabilirsiniz;
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
values.map(v => v + k)
}
Serbest değişken içermeyen bir fonksiyonun özel bir kapatma durumu olduğuna dikkat edin (boş bir serbest değişkenler kümesiyle). Benzer şekilde, anonim bir işlev , anonim bir kapatmanın özel bir durumudur; yani anonim bir işlev, serbest değişken içermeyen bir anonim kapatmadı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 yaratılan değeri kullanacaktır closure
. Döndürülen alertValue
işlevin ad alanı, closure
değişkenin bulunduğu ad alanına bağlanır . Tüm işlevi closure
sildiğinizde, değişkenin değeri silinir, ancak o zamana kadar, alertValue
işlev her zaman değişkenin değerini okuyabilir / yazabilir closure
.
Bu kodu çalıştırırsanız, ilk yineleme closure
değişkene 0 değerini atar ve işlevi yeniden yazar:
var closure_example = function(){
alertValue : function(){
closure++;
alert(closure);
}
}
Ve işlevi yürütmek alertValue
için yerel değişkene ihtiyaç duyduğundan, closure
önceden atanmış yerel değişkenin değerine bağlanır closure
.
Ve şimdi, closure_example
fonksiyonu her çağırışınızda, closure
değişkenin artan değerini yazacaktır çünkü alert(closure)
bağlı.
closure_example.alertValue()//alerts value 1
closure_example.alertValue()//alerts value 2
closure_example.alertValue()//alerts value 3
//etc.
Bir "kapanış", esasen, bir paket halinde birleştirilen bazı yerel devletler ve bazı kodlardır. Tipik olarak, yerel durum çevreleyen (sözcüksel) bir kapsamdan gelir ve kod (esasen) daha sonra dışa döndürülen bir iç işlevdir. Daha sonra kapatma, iç fonksiyonun gördüğü yakalanan değişkenlerin ve iç fonksiyonun kodunun bir kombinasyonudur.
Tanıdık olmadığından, açıklanması biraz zor olan şeylerden biri.
Geçmişte başarılı bir şekilde kullandığım bir benzetme “kitap” olarak adlandırdığımız bir şeye sahip olduğumuzu düşünün, oda kapanışında, “kitap”, orada, TAOCP köşesinde, masa kapağında, oradaki kopyadır. Bir Dresden Files kitabının kopyası budur. Bu nedenle, ne kapattığınıza bağlı olarak, 'kitabı bana ver' kodu farklı şeylerin ortaya çıkmasına neden olur. ”
static
Yerel değişkenli bir C işlevi kapatma olarak kabul edilebilir mi? Haskell'deki kapanışlar devleti içeriyor mu?
static
yerel bir değişken ile, tam olarak bir tane vardır).
“Devlet” kavramını tanımlamaksızın kapatmanın ne olduğunu tanımlamak zor.
Temel olarak, fonksiyonlara birinci sınıf değerler gibi davranan, tam anlamsal kapsamı olan bir dilde, özel bir şey olur. Eğer böyle bir şey yaparsam:
function foo(x)
return x
end
x = foo
Değişken x
sadece referans vermekle function foo()
kalmaz, aynı zamanda son dönüşte kalan devlete de atıfta bulunur foo
. Asıl sihir foo
, kendi kapsamı içinde daha başka tanımlanmış fonksiyonlara sahip olduğunda gerçekleşir ; kendi mini ortamı gibi ('normalde olduğu gibi' küresel bir ortamda fonksiyonları tanımlarız).
İşlevsel olarak, birden fazla işlev çağrısı sırasında yerel bir değişkenin durumunu koruyan C ++ (C?) 'Statik' anahtar sözcüğüyle aynı sorunların çoğunu çözebilir; bununla birlikte, fonksiyonlar birinci sınıf değerler olduğu için aynı prensibi (statik değişken) bir fonksiyona uygulamak gibidir; kapatma, tüm durumun kaydedilmesi için destek ekler (C ++ 'ın statik işlevleriyle ilgisi yoktur).
İşlevleri birinci sınıf değerler olarak kabul etmek ve kapanışlar için destek eklemek, aynı fonksiyonun birden fazla örneğini bellekte (sınıflara benzer şekilde) alabileceğiniz anlamına da gelir. 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
Zorlaşabilir ve muhtemelen dilden dile değişebilir, ancak Lua'da görünen o ki, bir işlev yürütüldüğünde durumunun sıfırlandı. Bunu söylüyorum, çünkü yukarıdaki kodun sonuçları, myclosure
fonksiyona / duruma doğrudan erişirsek (döndürdüğü anonim işlev yerine), pvalue
10'a geri döndürüleceğimizden farklı olacaktır; ancak gizlilik durumuna x (adsız işlev) üzerinden erişirsek, pvalue
bunun canlı ve bellekte bir yerde olduğunu görebilirsiniz . Bundan biraz daha fazlası olduğundan şüpheleniyorum, belki birileri uygulamanın doğasını daha iyi açıklayabilir.
Not: C ++ 11'in bir yalamasını bilmiyorum (önceki sürümlerde olanlar dışında), bunun C ++ 11 ve Lua'daki kapanışlar arasında bir karşılaştırma olmadığını unutmayın. Ayrıca, Lua'dan C ++ 'ya çizilen tüm' çizgiler ', statik değişkenlerle benzerlik gösterir ve kapanışlar% 100 değildir; bazen benzer sorunları çözmek için kullanılsalar bile.
Emin değilim, yukarıdaki kod örneğinde, adsız işlevin mi, yoksa üst düzey işlevin mi kapanma olarak değerlendirileceği mi?
Kapatma, ilişkili durumu olan bir işlevdir:
Perl'de şöyle kapaklar yaratırsınız:
#!/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ıza da olanak 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 fonksiyon düşünelim:
function f1(x) {
// ... something
}
Bu işleve bir üst düzey işlev adı verilir çünkü başka bir işlevin içine yerleştirilmez. Her JavaScript işlevi, kendisiyle "Kapsam Zinciri" olarak adlandırılan nesnelerin bir listesini oluşturur . Bu kapsam zinciri sıralı bir nesne listesidir. Bu nesnelerin her biri bazı değişkenleri tanımlar.
Üst düzey fonksiyonlarda, kapsam zinciri tek bir nesneden, genel nesneden oluşur. Örneğin, f1
yukarıdaki işlev , tüm global değişkenleri tanımlayan içinde tek bir nesneye sahip olan bir kapsam zincirine sahiptir. (buradaki "nesne" teriminin, JavaScript nesnesi anlamına gelmediğini, yalnızca JavaScript'in değişkenleri "arayabildiği" değişken bir kapsayıcı olarak çalışan, uygulama tarafından tanımlanmış bir nesne olduğunu unutmayın.)
Bu işlev çağrıldığında, JavaScript "Aktivasyon nesnesi" denilen bir şey yaratır ve onu kapsam zincirinin en üstüne yerleştirir. Bu nesne tüm yerel değişkenleri içerir (örneğin x
burada). Dolayısıyla şimdi kapsam zincirinde iki nesnemiz var: birincisi aktivasyon nesnesi ve altındaki ise küresel nesne.
Dikkatlice, iki nesnenin farklı zamanlarda kapsam zincirine konulduğunu unutmayın. Genel nesne, işlev tanımlandığında (yani, JavaScript işlevi ayrıştırdığında ve işlev nesnesini oluştururken) yerleştirilir ve işlev çağrıldığında etkinleştirme nesnesi girer.
Yani şimdi bunu biliyoruz:
İç içe geçmiş fonksiyonlarla uğraşırken durum ilginçleşiyor. Öyleyse bir tane yaratalım:
function f1(x) {
function f2(y) {
// ... something
}
}
Ne zaman f1
tanımlanmış olur biz sadece küresel nesne içeren bir kapsam zinciri olsun.
Şimdi f1
çağrıldığında, kapsam zinciri f1
aktivasyon nesnesini alır. Bu aktivasyon nesnesi değişkeni x
ve f2
bir işlev olan değişkeni içerir. Ve f2
tanımlandığını not edin . Bu nedenle, bu noktada, JavaScript, bunun için yeni bir kapsam zinciri kaydeder f2
. Bu iç işlev için kaydedilen kapsam zinciri, geçerli geçerli zincirdir. Geçerli kapsam zinciri yürürlükte f1
olanıdır. Bu nedenle f2
'in kapsamı zinciridir f1
s' mevcut aktivasyon nesnesi içeren - kapsam zinciri f1
ve küresel nesne.
Çağrıldığında f2
, y
içerdiği aktivasyon nesnesini, aktivasyon nesnesini f1
ve genel nesnesini içeren kapsam zincirine ekledi .
İçinde tanımlanmış başka bir iç içe geçmiş işlev varsa f2
, kapsam zinciri tanım zamanında üç nesne (iki dış işlevin 2 etkinleştirme nesnesi ve genel nesne) ve başlatma zamanında 4 nesne içerir.
Şimdi, kapsam zincirinin nasıl çalıştığını anlıyoruz ama henüz kapanmalardan bahsetmedik.
Bir fonksiyon nesnesinin ve fonksiyon değişkenlerinin çözümlendiği bir kapsam (bir dizi değişken bağlama) kombinasyonuna bilgisayar bilimi literatüründe kapatma denir - JavaScript David Flanagan'ın kesin rehberi
Çoğu işlev, işlev tanımlandığında geçerli olan aynı kapsam zinciri kullanılarak çağrılır ve bununla ilgili bir kapanmanın olması önemli değildir. Kapaklar, tanımlandıklarında geçerli olandan farklı bir kapsam zinciri altında çalıştırıldığında ilginçleşir. Bu, genellikle 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, bu etkinleştirme nesnesi kapsam zincirinden kaldırılır. Yuvalanmış işlev yoksa, etkinleştirme nesnesine daha fazla atıfta bulunulmaz ve çöp toplanır. Tanımlanmış iç içe geçmiş işlevler varsa, bu işlevlerin her birinin kapsam zincirine bir referansı vardır ve bu kapsam zinciri etkinleştirme nesnesini ifade eder.
Bununla birlikte, iç içe geçmiş bu işlevler nesneleri dış işlevleri içinde kaldıysa, o zaman kendileri de atıfta bulundukları etkinleştirme nesnesinin yanı sıra toplanacaklardır. Ancak, işlev iç içe geçmiş bir işlevi tanımlar ve onu döndürür veya başka bir yerde bir özelliğe depolarsa, iç içe geçmiş işleve harici bir başvuru olacaktır. Toplanan çöpler olmayacak ve atıfta bulunduğu etkinleştirme nesnesi de çöp toplanmayacaktır.
Yukarıdaki örnekte, dönmeyen f2
gelen f1
bir çağrı zaman, bu nedenle, f1
geri dönüş, kendi aktivasyon nesne toplanan kapsamı zinciri ve çöp kaldırılır. Ama böyle bir şeyimiz olsaydı:
function f1(x) {
function f2(y) {
// ... something
}
return f2;
}
Burada geri dönen f2
, aktivasyon nesnesini içerecek bir kapsam zincirine sahip olacak f1
ve dolayısıyla çöp toplanmayacaktır. Bu noktada, eğer ararsak f2
, dışarı f1
çıksak x
bile değişkene erişebilecek f1
.
Dolayısıyla bir fonksiyonun onun kapsam zincirini onunla birlikte tuttuğunu ve kapsam zinciri ile dış fonksiyonların tüm aktivasyon nesnelerinin geldiğini görebiliriz. Bu kapanmanın özüdür. JavaScript’teki işlevlerin "kapsamsal kapsamda " olduğunu , yani çağrıldıklarında etkin olan kapsamın aksine tanımlandıklarında etkin olan kapsamı koruduklarını söylüyoruz.
Özel değişkenlere yaklaşma, olaya dayalı programlama, kısmi uygulama vb. Gibi kapanışları içeren çok sayıda 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.
Bir kapatma derleyici optimizasyonu (aka sentaktik şeker?). Bazı insanlar buna Zavallı Adam'ın Nesnesi olarak da bahsetti .
Eric Lippert'in cevabına bakınız : (aşağıda alıntı)
Derleyici şöyle bir kod üretecektir:
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;
}
Mantıklı olmak?
Ayrıca, karşılaştırmalar istediniz. VB ve JScript, hemen hemen aynı şekilde kapaklar oluşturur.