Javascript'i anonim işlevlere karşı kapatma

Arkadaşım ve ben şu anda JS'de neyin kapandığını ve neyin olmadığını tartışıyoruz. Sadece bunu doğru bir şekilde anladığımızdan emin olmak istiyoruz.

Bu örneği al. Bir sayma döngümüz var ve konsolda bir sayaç değişkeni yazdırmak istiyorsunuz. Bu nedenle, N değerinin N değerinden N yazdırmamasını sağlamak için sayaç değişkeninin değerini sabitlemek için setTimeout ve kapaklarını kullanırız.

Kapatma olmadan yanlış karar verilmesi veya kapanmaya yakın herhangi bir şey:

 for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 1000); } 

Elbette, bu döngüden sonra i değerinin 10 katını, yani 10'unu yazdıracaktır.

Böylece girişimi şöyle oldu:

 for(var i = 0; i < 10; i++) { (function(){ var i2 = i; setTimeout(function(){ console.log(i2); }, 1000) })(); } 

beklendiği gibi 0 - 9 arasında yazdırın.

Ben onu kapatmak için kapatmayı kullanmadığını söyledim, ama olduğu konusunda ısrar ediyor. Başka bir setTimeout bir for döngü gövdesi yerleştirerek (anonim işlevini setTimeout geçirerek) başka bir setTimeout yerleştirerek kapak kullanmayacağını kanıtladım, tekrar 10 kez 10 yazarak, fonksiyonunu var olarak var ve döngüden sonra da uygularsam aynı şey olur. 10 kez 10 yazıyorum. Bu nedenle, benim argümanım, i değerini düzelttiği, versiyonunu kapatmadığı şeklinde.

Benim girişimim:

 for(var i = 0; i < 10; i++) { setTimeout((function(i2){ return function() { console.log(i2); } })(i), 1000); } 

Böylece, i düzeltirim (yakından i2 olarak adlandırılır), ama şimdi başka bir işlev i2 . Benim durumumda setTimeout işlevine geçirilen işlev gerçekten i yakalar.

Şimdi kim kapanışı kullanıyor ve kim kullanmıyor?

Her iki çözümün de konsolda 0 - 9 arasında yazdırıldığına dikkat edin, bu nedenle orijinal sorunu çözüyorlar, ancak bu iki çözümden hangisinin bunu yapmak için kapama kullandığını anlamak istiyoruz.

513
17 окт. leemes 17 Ekim ayarlayın . 2012-10-17 11:36 '12 11:36 2012-10-17 11:36
@ 11 cevaplar

Editörün notu: Bu yazıda açıklandığı gibi, JavaScript'teki tüm işlevler kilitlerdir. Ancak, sadece teorik bakış açısından ilginç olan bu fonksiyonların alt kümesinin tanımlanması ile ilgileniyoruz . Gelecekte, bir kelimeyi kapatmak için yapılan herhangi bir referans, aksi belirtilmedikçe, bu fonksiyon alt grubuna atıfta bulunacaktır.

Basit kapatma açıklaması:

  • Bir işlev al. F olarak adlandırılsın.
  • Tüm değişkenlerin listesi
  • Değişkenler iki tür olabilir:
    • Yerel Değişkenler (İlişkili Değişkenler)
    • Yerel olmayan değişkenler (serbest değişkenler)
  • F serbest değişkenlere sahip değilse, bu bir kapatma olamaz.
  • F herhangi bir serbest değişkene sahipse (F ana bölgede tanımlanmış olan), o zaman:
    • Serbest değişken a'nın bağlı olduğu yalnızca bir ebeveyn bölge F olmalıdır.
    • F, dış ana bölgeden bir bağlantı ise, o zaman serbest bir değişken için kapanır.
    • Serbest değişken , F kapağının değerlemesi olarak adlandırılır.

Şimdi bunu kimin kullandığını ve kimin kullanmadığını bulmak için kullanalım (açıklama için işlevi çağırdım):

1. Durum: arkadaşınızın programı

 for (var i = 0; i < 10; i++) { (function f() { var i2 = i; setTimeout(function g() { console.log(i2); }, 1000); })(); } 

Yukarıdaki programda iki fonksiyon vardır: f ve g . Kapalı olup olmadıklarına bakın:

f :

  • Değişkenleri listele:
    • i2 yerel bir değişkendir .
    • i serbest bir değişkendir.
    • setTimeout serbest bir değişkendir.
    • g yerel bir değişkendir.
    • console serbest bir değişkendir.
  • Her serbest değişkenin bağlı olduğu ana alanı bulun:
    • küresel bir alana bağlıyım.
    • setTimeout global bir kapsama bağlıdır.
    • console global kapsamı ile sınırlıdır.
  • İşlev hangi kapsamda başvurulan ? Global alan .
    • Bu nedenle, f tarafından kapatılmadım .
    • Bu nedenle, setTimeout f tarafından kapatılmaz .
    • Sonuç olarak, console f üzerinde kapalı değil.

Bu nedenle, f işlevi bir kapanma değildir.

g :

  • Değişkenleri listele:
    • console serbest bir değişkendir.
    • i2 serbest bir değişkendir.
  • Her serbest değişkenin bağlı olduğu ana alanı bulun:
    • console global kapsamı ile sınırlıdır.
    • i2 f alanına bağlanır .
  • İşlev hangi kapsamda başvurulan ? setTimeout alanı .
    • Sonuç olarak, console g tarafından kapatılmaz .
    • Bu nedenle, i2 g tarafından kapatılır .

Bu nedenle, g işlevi, setTimeout öğesinden belirtildiğinde , serbest değişken i2 (g'nin değeridir) için bir kapatmadır.

Senin için kötü: arkadaşın kapanış kullanıyor. Dahili fonksiyon bir kapanış.

2. Durum: Programınız

 for (var i = 0; i < 10; i++) { setTimeout((function f(i2) { return function g() { console.log(i2); }; })(i), 1000); } 

Yukarıdaki programda iki fonksiyon vardır: f ve g . Kapalı olup olmadıklarına bakın:

f :

border=0
  • Değişkenleri listele:
    • i2 yerel bir değişkendir .
    • g yerel bir değişkendir.
    • console serbest bir değişkendir.
  • Her serbest değişkenin bağlı olduğu ana alanı bulun:
    • console global kapsamı ile sınırlıdır.
  • İşlev hangi kapsamda başvurulan ? Global alan .
    • Sonuç olarak, console f üzerinde kapalı değil.

Bu nedenle, f işlevi bir kapanma değildir.

g :

  • Değişkenleri listele:
    • console serbest bir değişkendir.
    • i2 serbest bir değişkendir.
  • Her serbest değişkenin bağlı olduğu ana alanı bulun:
    • console global kapsamı ile sınırlıdır.
    • i2 f alanına bağlanır .
  • İşlev hangi kapsamda başvurulan ? setTimeout alanı .
    • Sonuç olarak, console g tarafından kapatılmaz .
    • Bu nedenle, i2 g tarafından kapatılır .

Bu nedenle, g işlevi, setTimeout öğesinden belirtildiğinde , i2 serbest değişkeni (g'nin değeridir) için bir kapatmadır.

Senin için iyi: kapatma kullanıyorsun. Dahili fonksiyon bir kapanış.

Demek siz ve arkadaşınız kapatıyorsunuz. Tartışmayı bırak. Umarım kapatma kavramını ve her ikiniz için onları nasıl tanımlayacağımı temizledim.

Düzenleme: Tüm işlevlerin kapatılma nedenlerinin basit bir açıklaması (@ Krediler):

İlk önce, aşağıdaki programı göz önünde bulundurun (bu kontrol ):

bir alternatif ): 

bu cevaba bakınız ) - bir message içermez). 
  • Bir programı yürüttüğümüzde, message aslında uyarıldığını fark ediyoruz .
  • Bundan ne çıkarıyoruz?

    • JavaScript tercümanları, diğer işlevlerden başka kapatmaya işaret etmez.
    • Her fonksiyon, beraberinde bir zincir zincir içerir . Kapatmanın ayrı bir bağlantı ortamı yoktur.
    • Kapanış diğer fonksiyonlar gibidir. Biz sadece ilginç bir durum olduğu için onlara ait oldukları alanın dışındaki bir alanda listelendiklerinde kapatma deriz.
    609
    17 окт. Tarafından cevap Aadit M Şah 17 Eki 2012-10-17 13:01 '12, 1:01 pm 2012-10-17 13:01

    closure tanımına uygun olarak:

    "Kapanış", bu değişkenleri bağlayan çevre ile birlikte ( serbest ifadeyi "kapatan") serbest değişkenleri olan bir ifadedir (genellikle bir işlevdir).

    border=0

    İşlev dışında tanımlanmış bir değişkeni kullanan bir işlevi tanımlarsanız, closure kullanırsınız. (bir değişkeni serbest değişken olarak adlandırırız ).
    Hepsi closure kullanır (ilk örnekte bile).

    89
    17 окт. Cevap 17 kev . 2012-10-17 11:59 '12, 11:59 2012-10-17 11:59

    Özetle, Javascript Kapakları , fonksiyonların bir lexico-parent fonksiyonunda bildirilen bir değişkene erişmesine izin verir.

    Daha ayrıntılı açıklamaya bakınız. Kapanışları anlamak için, JavaScript kapsamlarının nasıl değişken olduğunu anlamak önemlidir.

    manzaraları

    İşlevler JavaScript alanlarında tanımlanmıştır. Her fonksiyon yeni bir alan tanımlar.

    Aşağıdaki örneği düşünün:

     function f() {//begin of scope f var foo='hello'; //foo is declared in scope f for(var i=0;i<2;i++){//i is declared in scope f //the for loop is not a function, therefore we are still in scope f var bar = 'Am I accessible?';//bar is declared in scope f console.log(foo); } console.log(i); console.log(bar); }//end of scope f 

    f çağrı yazdırır

     hello hello 2 Am I Accessible? 

    Şimdi, başka bir fonksiyon çerçevesinde tanımlanan bir fonksiyona g sahip olduğumuzda, f durumunu düşünün.

     function f() {//begin of scope f function g() {//being of scope g  }//end of scope g  }//end of scope f 

    Sözlüksel ebeveyni arayacağız g . Daha önce açıklandığı gibi, şimdi 2 alanımız var; f alanı ve g alanı.

    Ancak bir kapsam, diğer fonksiyonun kapsamı içinde olduğu gibi, çocuk fonksiyonun kapsamı da ana fonksiyon kapsamı içinde midir? Üst işlev kapsamında açıklanan değişkenlere ne olur; çocuk fonksiyonu kapsamında bunlara erişebilir miyim? Tam olarak nerede kapaklar.

    panjurlar

    JavaScript’te, g işlevi yalnızca g etki alanında bildirilen değişkenlere erişemez, aynı zamanda f ana işlev bölgesinde belirtilen bildirilen değişkenlere de erişebilir.

    Aşağıdakileri göz önünde bulundurun:

     function f()//lexical parent function {//begin of scope f var foo='hello'; //foo declared in scope f function g() {//being of scope g var bar='bla'; //bar declared in scope g console.log(foo); }//end of scope g g(); console.log(bar); }//end of scope f 

    f çağrı yazdırır

     hello undefined 

    console.log(foo); bak console.log(foo); . Bu aşamada, g alanındayız ve f alanında bildirilen foo değişkenine erişmeye çalışıyoruz. Ancak, yukarıda belirtildiği gibi, burada gerçekleşen sözcüksel ebeveyn işlevinde belirtilen herhangi bir değişkene erişebiliriz; g , sözcük ebeveynidir f . Bu nedenle hello basılmıştır.
    Şimdi console.log(bar); . Şu anda f etki alanındayız ve g etki alanında bildirilen değişken bar erişmeye çalışıyoruz. bar geçerli bölgede bildirilmez ve g f ebeveyni değildir, bu nedenle bar tanımsızdır

    Aslında, “büyük ebeveyn” in sözcüksel işlevinde bildirilen değişkenlere de erişebiliriz. Bu nedenle, g fonksiyonunda tanımlanmış bir fonksiyon varsa, g

     function f() {//begin of scope f function g() {//being of scope g function h() {//being of scope h  }//end of scope h  }//end of scope g  }//end of scope f 

    o zaman h , h , g ve f fonksiyonlarının etki alanında bildirilen tüm değişkenlere erişebilecek. Bu kapanışta yapılır. JavaScript'te kapatma , sözcüksel ana işlevinde, sözcüksel büyük ana işlevinde, sözcüksel büyük ana işlevinde, vb. Bildirilen herhangi bir değişkene erişmemizi sağlar. Bu bir zincir zincir olarak düşünülebilir. ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... sözcüksel üst öğeye sahip olmayan son üst düzey işlevine kadar.

    Pencere nesnesi

    Aslında, zincir son ana işlevde durmaz. Başka bir özel ölçek var; küresel alan . Bir işlevde bildirilmeyen her değişken, global kapsamda bildirilmiş olarak kabul edilir. Küresel kapsamın iki özelliği vardır:

    • Global kapsamda belirtilen herhangi bir değişken her yerde mevcuttur
    • Genel kapsamda bildirilen değişkenler, window nesnesinin özelliklerine karşılık gelir.

    Bu nedenle, foo değişkenini global kapsamda ilan etmenin sadece iki yolu vardır; bir işlevde bildirmeyerek veya window nesnesinin foo özelliğini ayarlayarak.

    Her iki deneme de kilit kullanıyor

    Artık daha ayrıntılı bir açıklama okuduğunuza göre, her iki çözümün de kilit kullandığı çok açık görünebilir. Ama, elbette, bir kanıt yapalım.

    Yeni bir programlama dili oluşturmama izin verin; Javascript kapatma yok Adından da anlaşılacağı gibi, JavaScript-No-Closure, JavaScript’le aynıdır, ancak Kapanışları desteklememektedir.

    Başka bir deyişle:

     var foo = 'hello'; function f(){console.log(foo)}; f(); //JavaScript-No-Closure prints undefined //JavaSript prints hello 

    Peki, JavaScript-No-Closure'un ilk çözümü ile ne olacağını görelim;

     for(var i = 0; i < 10; i++) { (function(){ var i2 = i; setTimeout(function(){ console.log(i2); //i2 is undefined in JavaScript-No-Closure }, 1000) })(); } 

    bu nedenle, JavaScript-No-Closure'da 10 kez undefined yazdırılacaktır.

    Bu nedenle, ilk çözüm bir kapatma kullanır.

    İkinci çözüme bakalım:

     for(var i = 0; i < 10; i++) { setTimeout((function(i2){ return function() { console.log(i2); //i2 is undefined in JavaScript-No-Closure } })(i), 1000); } 

    bu nedenle, JavaScript-No-Closure'da 10 kez undefined yazdırılacaktır.

    Her iki çözüm de kapanış kullanır.

    Düzenleme: Bu 3 kod parçasının global kapsamda tanımlanmadığı varsayılmaktadır. Aksi takdirde, foo ve i değişkenleri, window nesnesine bağlanır ve bu nedenle hem JavaScript hem de JavaScript-No-Closure'da window nesnesi üzerinden erişilebilir.

    46
    17 окт. Cevap brillout 17 ekim olarak verildi . 2012-10-17 12:33 '12 12:33 2012-10-17 12:33

    Hiç kimsenin bunu açıklamasından memnun olmadım.

    Kapanışları anlamanın anahtarı, JS'nin kapanmadan ne olacağını anlamaktır.

    Kapanmadan bir hataya neden olur

     function outerFunc(){ var outerVar = 'an outerFunc var'; return function(){ alert(outerVar); } } outerFunc()(); //returns inner function and fires it 

    Dış func, javascript'in hayali bir kapalı devre dışı bırakılmış versiyonuna geri döner dönmez, outerVar ile olan bağlantı çöp toplayacak ve içsel fonksiyonun ifade ettiği yerde hiçbir şey bırakmayacaktır.

    Kapanış, aslında, bir iç işlev, bir işlevin dış değişkenlerini ifade ettiğinde bu çizgileri vuran ve buna izin veren özel kurallardır. Kapanışta, vars işaretli referanslar, harici fonksiyonun yürütülmesinden sonra bile korunur veya bu noktayı hatırlamanıza yardımcı olursa “kapatılır”.

    Kapanırken bile, yerelin yaşam döngüsü, yerel sakinlerine atıfta bulunan dahili fonksiyonları olmayan bir fonksiyonda, kapalı versiyondaki gibi çalışır. İşlev tamamlandığında, yerel sakinler toplanan çöpü alır.

    Bağlantının iç var harici func içerisine girdiği an, ancak bu, bir kapı pervazı gibi, bahsedilen varslar için çöp toplama yoluna yerleştirilir.

    Belki de kapanışlara bakmak için daha doğru bir yol, iç fonksiyonun temel olarak iç kapsamı kendi kapsamı olarak kullanmasıdır.

    Ancak bununla ilişkili içerik aslında sabittir ve anlık görüntü olarak değil. Harici işlevi artırmaya ve kaydetmeye devam eden döndürülen iç işlevi yeniden etkinleştirerek, yerel var daha yüksek değerleri uyarır.

     function outerFunc(){ var incrementMe = 0; return function(){ incrementMe++; console.log(incrementMe); } } var inc = outerFunc(); inc(); //logs 1 inc(); //logs 2 
    19
    18 окт. Tarafından cevap Erik Reppen 18 ekim. 2012-10-18 04:51 '12, 04:51 2012-10-18 04:51

    İkiniz de kapatma kullanıyorsunuz.

    Burada Wikipedia tanımı ile gidiyorum:

    Bilgisayar bilimlerinde, kapatma (ayrıca sözcüksel kapanma veya kapanma işlevi), referans ortamı ile birlikte bir fonksiyon veya işlev referansıdır - bu fonksiyonun yerel olmayan değişkenlerinin (ayrıca serbest değişkenler de denir) bağlantısını gösteren bir tablo. Kapanış - basit bir fonksiyon göstergesinin aksine - sözcüksel kapsam dışında olsalar bile, bu yerel olmayan değişkenlere erişime izin verir.

    Arkadaşınızın girişimi açıkça yerel olmayan i değişkenini kullanır, değerini alır ve yerel i2 depolamak için bir kopyasını i2 .

    Kendi denemeniz, i (alandaki çağrı düğümünde) i anonim işlevine argüman olarak iletir. Bu yakın olmasa da, bu fonksiyon aynı i2 karşılık gelen başka bir fonksiyon döndürür. Dahili anonim fonksiyon i2 yerelde olmadığından, bir kapatma oluşturur.

    16
    17 окт. Jon için Cevaplayın Oct 17 2012-10-17 12:08 '12 12:08 2012-10-17 12:08

    Siz ve arkadaşınız kapatmayı kullanın:

    Kapatma, iki şeyi birleştiren özel bir nesne türüdür: işlev ve işlevin oluşturulduğu çevre. Çevre, kapatmanın yaratıldığı sırada kapsam dahilindeki tüm yerel değişkenlerden oluşur.

    MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

    Arkadaşınızın kod fonksiyonunda, function(){ console.log(i2); } function(){ console.log(i2); } , isimsiz bir işlev function(){ var i2 = i; ... kapanışı içinde tanımlanmış function(){ var i2 = i; ... function(){ var i2 = i; ... ve i2 yerel değişkenini okuyabilir / yazabilir.

    İşlev kodunuzda function(){ console.log(i2); } function(){ console.log(i2); } , kapanış işlevi function(i2){ return ... içinde tanımlanan function(i2){ return ... ve yerel değerli i2 okuyabilir / yazabilir (bu durumda parametre olarak bildirilir).

    Her iki durumda da, function function(){ console.log(i2); } function(){ console.log(i2); } sonra setTimeout gider.

    Başka bir eşdeğer (ancak daha az bellek kullanımı ile):

     function fGenerator(i2){ return function(){ console.log(i2); } } for(var i = 0; i < 10; i++) { setTimeout(fGenerator(i), 1000); } 
    12
    17 окт. Cevap Andrew D. 17 Ekim'de verildi . 2012-10-17 12:03 '12 12:03 2012-10-17 12:03

    kapanış

    Kapanış bir işlev değildir, ifade değildir. Fonksiyonun dışında kullanılan ve fonksiyonun içinde kullanılan değişkenlerin bir nevi "anlık görüntüsü" olarak kabul edilmelidir. Dilbilgisel olarak şöyle söylenmelidir: "değişkenlerin kapatılması".

    Başka bir deyişle: kapatma, işlevin bağlı olduğu değişkenlerin karşılık gelen içeriğinin bir kopyasıdır.

    Bir kez daha (naif): kapanış, parametre olarak geçilmeyen değişkenlere erişebilir.

    Bu işlevsel kavramların kullandığınız programlama diline / ortama bağlı olduğunu unutmayın. JavaScript'te kapatma, sözcük kapsamına bağlıdır (çoğu c dilinde geçerlidir).

    Bu nedenle, bir işlevi döndürmek temel olarak adsız / adsız bir işlevi döndürür. Bir fonksiyon erişim değişkeni, bir parametre olarak ve onun (sözcüksel) alanı içinde geçilmediğinde kapatıldı.

    Yani, örneklerinize ilişkin:

     // 1 for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); // closure, only when loop finishes within 1000 ms, }, 1000); // i = 10 for all functions } // 2 for(var i = 0; i < 10; i++) { (function(){ var i2 = i; // closure of i (lexical scope: for-loop) setTimeout(function(){ console.log(i2); // closure of i2 (lexical scope:outer function) }, 1000) })(); } // 3 for(var i = 0; i < 10; i++) { setTimeout((function(i2){ return function() { console.log(i2); // closure of i2 (outer scope) } })(i), 1000); // param access i (no closure) }