Soru Döngüler içinde JavaScript kapatma - basit pratik örnek


var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function() {          // and store them in funcs
    console.log("My value: " + i); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

Bunu çıkarır:

Benim değerin: 3
  Benim değerin: 3
  Benim değerin: 3

Oysa çıktısını almak isterim:

Benim değerim: 0
  Benim değerin: 1
  Benim değerin: 2


Aynı sorun, işlevi çalıştırmakta gecikme olay dinleyicileri kullanılarak meydana geldiğinde oluşur:

var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {          // let's create 3 functions
  buttons[i].addEventListener("click", function() { // as event listeners
    console.log("My value: " + i);                  // each should log its value.
  });
}
<button>0</button><br>
<button>1</button><br>
<button>2</button>

… Veya eşzamansız kod, ör. Promises kullanarak:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for(var i = 0; i < 3; i++){
  wait(i * 100).then(() => console.log(i)); // Log `i` as soon as each promise resolves.
}

Bu temel sorunun çözümü nedir?


2324
2018-04-15 06:06


Menşei


İstemediğinden emin misin funcs sayısal endeksleri kullanıyorsanız, bir dizi olmak için? Sadece bir kafa. - DanMan
Bu gerçekten kafa karıştırıcı bir sorundur. Bu makale anlamada bana yardımcı. Başkalarına da yardım edebilir. - user3199690
Diğer basit ve açıklanmış bir çözüm: 1) İç içe Fonksiyonlar "yukarıdaki" kapsamına erişim hakkına sahip olmak; 2) bir kapatma çözüm... "Bir kapatma, ana işlev kapatıldıktan sonra bile ana kapsama erişimi olan bir işlevdir". - Peter Krauss
Daha iyi Unserstanding için bu bağlantıya bakın javascript.info/tutorial/advanced-functions - Saurabh Ahuja
İçinde ES6, önemsiz bir çözüm değişken ilan etmektir ben ile letDöngünün gövdesine kadar uzanır. - Tomas Nikodym


Cevaplar:


Sorun şu ki değişken iAnonim işlevlerin her birinde, işlevin dışında aynı değişkene bağlanır.

Klasik çözüm: Kapaklar

Yapmak istediğiniz şey, her işlev içindeki değişkeni, işlevin dışında ayrı, değişmeyen bir değere bağlamaktır:

var funcs = [];

function createfunc(i) {
    return function() { console.log("My value: " + i); };
}

for (var i = 0; i < 3; i++) {
    funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Javascriptte yalnızca bir blok kapsamı olmadığından - yalnızca işlev kapsamı - işlev oluşturmayı yeni bir işlevle sararken, "i" değerinin sizin istediğiniz gibi kalmasını sağlarsınız.


2015 Çözümü: forEach

Nispeten yaygın kullanılabilirliği ile Array.prototype.forEach fonksiyon (2015'te), öncelikle bir dizi değer üzerinde yinelemeyi içeren durumlarda, .forEach() Her iterasyon için ayrı bir kapanış elde etmek için temiz ve doğal bir yol sağlar. Yani, bir dizi değer (DOM referansları, nesneler, her neyse) içeren bir dizi var ve her bir öğeye özgü geri çağrıların ayarlanmasından kaynaklanan bir sorun olduğu varsayılarak, bunu yapabilirsiniz:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

Fikir, geri çağırma işlevinin her bir çağrısının .forEach döngü kendi kapanışı olacak. Bu işleyiciye iletilen parametre, yinelemenin belirli adımına özgü dizi öğesidir. Eşzamansız bir geri çağırmada kullanılıyorsa, yinelemenin diğer adımlarında oluşturulan diğer geri çağrılardan herhangi biriyle çarpışmaz.

JQuery'de çalışıyorsanız, $.each() fonksiyon size benzer bir yetenek verir.


ES6 çözümü: let

JavaScript'in en yeni sürümü olan ECMAScript 6 (ES6) artık pek çok dökmeyen tarayıcı ve arka uç sisteminde uygulanmaya başlıyor. Ayrıca transpilers gibi Babil eski sistemlerde yeni özelliklerin kullanımına izin vermek için ES6'yı ES5'e dönüştürecek.

ES6 yeni tanıttı let ve const farklı kapsamda olan anahtar kelimeler vartemelli değişkenler. Örneğin, bir döngüde lettemelli dizin, döngü boyunca her yineleme yeni bir değere sahip olacak i her bir değerin döngü içinde kapsamlandığı yerlerde kodunuz beklediğiniz gibi çalışır. Çok fazla kaynak var, ama tavsiye ederim 2. sınıfın blok belirleme görevlisi büyük bir bilgi kaynağı olarak.

for (let i = 0; i < 3; i++) {
    funcs[i] = function() {
        console.log("My value: " + i);
    };
}

Bununla birlikte, Edge 14'ten önce IE9-IE11 ve Edge'in desteklendiğine dikkat edin let ama yukarıdaki yanlış olsun (yeni değiller) i her seferinde, yukarıdaki tüm fonksiyonlar 3 kullansaydık var). 14 kenarı sonunda doğru olur.


1799
2018-04-15 06:18



değil function createfunc(i) { return function() { console.log("My value: " + i); }; } hala kapanır çünkü değişken kullanır i? - アレックス
Ne yazık ki, bu cevap modası geçmiş ve hiç kimse altta doğru cevabı görmeyecek - Function.bind() şimdiye kadar kesinlikle tercih edilir, bkz. stackoverflow.com/a/19323214/785541. - Wladimir Palant
@Wladimir: Senin önerin .bind() olduğu "doğru cevap" doğru değil Her birinin kendi yeri var. İle .bind() bağlayıcı olmadan argümanları bağlayamazsınız this değer. Ayrıca bir kopyasını al i Bazen gerekli olan aramalar arasında geçiş yapma yeteneğine sahip olmayan argüman. Yani oldukça farklı yapılar var, bahsetmiyorum .bind() Uygulamalar tarihsel olarak yavaş olmuştur. Elbette basit örnekte işe yarayacak, ancak kapanışlar anlaşılması gereken önemli bir kavramdır ve bu da sorunun neyle ilgiliydi. - cookie monster
Lütfen geri dönüş için bu işlev hacklerini kullanmayı bırakın, aynı kapsam değişkenlerini tekrar kullanmamak için [] .forEach veya [] .map kullanın. - Christian Landgren
@ChristianLandgren: Bu sadece bir Array'ı tekrarlıyorsanız kullanışlıdır. Bu teknikler "hack" değildir. Onlar temel bilgi.


Deneyin:

var funcs = [];

for (var i = 0; i < 3; i++) {
    funcs[i] = (function(index) {
        return function() {
            console.log("My value: " + index);
        };
    }(i));
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Düzenle (2014):

Şahsen @ Aust'in düşünüyorum kullanımı hakkında daha yeni cevap .bind şimdi böyle bir şey yapmanın en iyi yolu. Ayrıca bir çizgi / alt çizgi var. _.partial İhtiyacınız olmadığında veya karışmak istediğinizde bind'ler thisArg.


340
2018-04-15 06:10



hakkında herhangi bir açıklama }(i)); ? - aswzen
@aswzen Bence geçer i argüman olarak index işlevine. - Jet Blue


Henüz bahsetmemiş olmanın başka bir yolu da Function.prototype.bind

var funcs = {};
for (var i = 0; i < 3; i++) {
  funcs[i] = function(x) {
    console.log('My value: ' + x);
  }.bind(this, i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();
}

GÜNCELLEŞTİRME

@Squint ve @mekdev tarafından işaret edildiği gibi, önce loop dışındaki işlevi yaratarak ve ardından sonuçları döngü içinde birleştirerek daha iyi bir performans elde edersiniz.

function log(x) {
  console.log('My value: ' + x);
}

var funcs = [];

for (var i = 0; i < 3; i++) {
  funcs[i] = log.bind(this, i);
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}


310
2017-10-11 16:41



Ben de bu günlerde yaptığım şey, aynı zamanda lo-dash / underscore gibi _.partial - Bjorn Tipling
.bind() ECMAScript 6 özellikleri ile büyük ölçüde eskimiş olacaktır. Ayrıca, bu aslında yineleme başına iki işlev oluşturur. Önce anonim, o zaman .bind(). Daha iyi kullanmak, onu döngü dışında oluşturmak, sonra .bind() İçerde.
Bu tetikleme JsHint değil - Bir döngü içinde işlevler yapmayın. ? Bu yola da girdim ama kod kalitesi araçlarını çalıştırdıktan sonra bir hayır. - mekdev
@squint @mekdev - İkiniz de haklısınız. İlk örneğim nasıl olduğunu göstermek için hızlı bir şekilde yazılmıştır. bind kullanıldı. Önerilerinize başka bir örnek ekledim. - Aust
Ben iki O (n) döngüsünün üzerinde hesaplama yapmak yerine, sadece yapmak için düşünüyorum (var i = 0; i <3; i ++) {log.call (this, i); } - user2290820


Bir Hemen Çağrılı İşlev İfadesi, bir indeks değişkeni eklemenin en basit ve okunaklı yolu:

for (var i = 0; i < 3; i++) {

    (function(index) {
        console.log('iterator: ' + index);
        //now you can also loop an ajax call here 
        //without losing track of the iterator value: $.ajax({});
    })(i);

}

Bu yineleyiciyi gönderir i olarak tanımladığımız anonim işleve index. Bu, değişkenin olduğu bir kapanma oluşturur i IIFE içindeki herhangi bir asenkron işlevsellikte daha sonra kullanılmak üzere kaydedilir.


234
2017-10-11 18:23



Daha fazla kod okunabilirliği için ve i ne, işlev parametresini yeniden adlandırmak istiyorum index. - Kyle Falconer
Diziyi tanımlamak için bu tekniği nasıl kullanırsınız Funcs Orijinal soruda açıklanmış mı? - Nico
@Nico Orijinal soruda gösterildiği gibi, kullanacağınız hariç index yerine i. - JLRishe
@JLRishe var funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); } - Nico
@Nico OP'nin özel durumunda, sadece sayıların üzerinde yineliyorlar, bu yüzden bu harika bir durum olmayacaktı .forEach()ama çoğu zaman, bir dizi ile başlıyorsa, forEach()gibi iyi bir seçimdir: var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; }); - JLRishe


Partiye geç kaldım, ama bugün bu konuyu araştırıyordum ve cevapların çoğunun Javascript'in kapsamları nasıl ele aldığını tamamen çözmediğini fark ettim.

Diğerlerinin de belirttiği gibi, problem içsel fonksiyonun aynı referans olduğunu gösteriyor. i değişken. Öyleyse neden her yinelemede yeni bir yerel değişken oluşturmuyoruz ve bunun yerine içsel işlev referansı var mı?

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    var ilocal = i; //create a new local variable
    funcs[i] = function() {
        console.log("My value: " + ilocal); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Daha önce olduğu gibi, her bir iç fonksiyonun atanan son değeri çıktı i, şimdi her iç işlev, sadece atanan son değeri çıkarır ilocal. Fakat her bir yinelemenin kendi başına olmaması gerekir. ilocal?

Çıkıyor, sorun bu. Her yineleme aynı kapsamı paylaşır, bu nedenle ilkinden sonraki her yineleme sadece üzerine yazılır. ilocal. itibaren MDN:

Önemli: JavaScript'in blok kapsamı yoktur. Bir blokla eklenen değişkenler, içerdiği işlev veya komut dosyasına göre belirlenir ve bunların ayarlanması etkileri, bloğun kendisinin ötesinde devam eder. Başka bir deyişle, blok ifadeleri bir kapsam oluşturmaz. "Bağımsız" bloklar geçerli sözdizimi olmasına rağmen, JavaScript'te bağımsız bloklar kullanmak istemezsiniz, çünkü C veya Java'da bu tür bloklar gibi bir şey yaptığını düşünüyorsanız, düşündüklerini yapmazlar.

Vurgu için tekrarlanan:

JavaScript'in blok kapsamı yok. Bir blok ile tanıtılan değişkenler, içeren işlev veya komut dosyasına ayrılır

Bunu kontrol ederek görebiliriz ilocal Her iterasyonda beyan etmeden önce:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
  console.log(ilocal);
  var ilocal = i;
}

İşte bu yüzden bu hata çok zor. Bir değişkeni yeniden bildiriyor olsanız da, Javascript bir hata yapmaz ve JSLint bir uyarı bile alamaz. Bu da, bunu çözmenin en iyi yolunun, kapağın avantajlarından yararlanmaktır. Bu, esas olarak, iç kapsamların dış kapsamları "içselleştirmesi" nedeniyle, iç işlevlerin Javascript'teki dış değişkenlere erişebileceği fikridir.

Closures

Bu aynı zamanda, iç fonksiyonların dış değişkenleri geri döndürse bile dış değişkenleri "tutun" ve onları canlı tutma anlamına gelir. Bunu kullanmak için, yalnızca yeni bir kapsam oluşturmak için bir sarmalayıcı işlevini oluşturup çağırıyoruz. ilocalyeni kapsamda ve kullanılan bir iç işlevi döndürür ilocal (aşağıda daha fazla açıklama):

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() { //create a new scope using a wrapper function
        var ilocal = i; //capture i into a local var
        return function() { //return the inner function
            console.log("My value: " + ilocal);
        };
    })(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Bir sarmalayıcı fonksiyonunun içindeki içsel fonksiyonun yaratılması içsel fonksiyona, sadece bir "kapama" erişebilen özel bir ortam sağlar. Böylece, sarmalayıcı işlevini her aradığımızda, kendi ayrı ortamıyla yeni bir iç işlev yaratıyoruz ve ilocal değişkenler çarpışmaz ve birbirinin üzerine yazmaz. Birkaç küçük optimizasyon, birçok SO kullanıcısı tarafından verilen son cevabı verir:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
    return function() { //return the inner function
        console.log("My value: " + ilocal);
    };
}

Güncelleştirme

ES6 şimdi ana akım ile, şimdi yeni kullanabiliriz let engelleme değişkenleri oluşturmak için anahtar kelime:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
    funcs[i] = function() {
        console.log("My value: " + i); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
    funcs[j]();
}

Şimdi ne kadar kolay olduğuna bakın! Daha fazla bilgi için bakınız bu cevapBilgimin kapalı olduğu.


127
2018-04-10 09:57



IIFE yöntemini nasıl açıkladığınızı beğendim. Onu arıyordum. Teşekkür ederim. - CapturedTree
Şimdi JavaScript kullanarak blok kapsam belirleme gibi bir şey var. let ve const anahtar kelimeler. Eğer bu cevap bunu içerecek şekilde genişletilecek olsaydı, bence küresel olarak çok daha faydalı olurdu. - Tiny Giant
@TinyGiant emin şey, hakkında bazı bilgiler ekledim let ve daha eksiksiz bir açıklama bağladı - woojoo666
@ woojoo666 Cevabınız, aynı şekilde iki alternatif URL’yi çağırmak için de kullanılabilir: i=0; while(i < 100) { setTimeout(function(){ window.open("https://www.bbc.com","_self") }, 3000); setTimeout(function(){ window.open("https://www.cnn.com","_self") }, 3000); i++ }? (getelementbyid ile window.open () yerini alabilir ......) - nutty about natty
@nuttyaboutnatty böyle bir geç cevap için özür dilerim. Örneğindeki kod zaten işe yaramış gibi görünmüyor. Kullanmıyorsun i zaman aşımı fonksiyonlarınızda, bir kapanmaya ihtiyacınız yoktur - woojoo666


ES6 artık yaygın olarak destekleniyorsa, bu sorunun en iyi cevabı değişti. ES6 sağlar let ve const Bu tam durum için anahtar kelimeler. Kapatmalarla uğraşmak yerine, sadece kullanabiliriz let Böyle bir döngü kapsamı değişkeni ayarlamak için:

var funcs = [];
for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

val Daha sonra, bu döngünün özel dönüşüne özgü bir nesneyi işaret eder ve ek kapatma notasyonu olmadan doğru değeri döndürür. Bu açıkça, bu problemi önemli ölçüde basitleştirir.

const benzer let Ek kısıtlama ile değişken adın ilk atamadan sonra yeni bir referansa geri dönülememesi.

Tarayıcı desteği, en yeni tarayıcı sürümlerini hedefleyenler için şimdi burada. const/let şu anda en yeni Firefox, Safari, Edge ve Chrome'da desteklenmektedir. Ayrıca Düğümde desteklenir ve Babel gibi inşa araçlarından yararlanarak her yerde kullanabilirsiniz. Burada çalışan bir örnek görebilirsiniz: http://jsfiddle.net/ben336/rbU4t/2/

Dokümanlar burada:

Bununla birlikte, Edge 14'ten önce IE9-IE11 ve Edge'in desteklendiğine dikkat edin let ama yukarıdaki yanlış olsun (yeni değiller) i her seferinde, yukarıdaki tüm fonksiyonlar 3 kullansaydık var). 14 kenarı sonunda doğru olur.


121
2018-05-21 03:04



Ne yazık ki, özellikle de mobil cihazlarda 'let' hala tam olarak desteklenmiyor. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... - MattC
16 Haziran’dan itibaren let iOS Safari, Opera Mini ve Safari 9 dışındaki tüm büyük tarayıcı sürümlerinde desteklenir. Evergreen tarayıcılar bunu destekler. Babel, yüksek uyumluluk modu açılmadan beklenen davranışı korumak için doğru şekilde aktarır. - Dan Pantry
@DanPantry hakkında bir güncelleme zamanı hakkında evet :) daha iyi bir şeyler, daha iyi uyumluluk bilgisi, dokümanlar ve daha fazla bilgi eklemek dahil olmak üzere, şeylerin mevcut durumunu yansıtmak için güncellendi. - Ben McCormick
Bu yüzden kodumuzu kullanmak için babeli kullanmıyoruz, bu yüzden ES6 / 7'yi desteklemeyen tarayıcılar neler olduğunu anlayabilir mi? - pixel 67


Bunu söylemenin başka bir yolu da i fonksiyonunuzda, işlevi yürütme zamanı değil, işlevi yürütme sırasında sınırlanır.

Kapatmayı oluşturduğunuzda, i Dış kapsamda tanımlanan değişkenin bir referansıdır, kapatmayı oluşturduğunuzda olduğu gibi bir kopyasını değil. Yürütme sırasında değerlendirilecektir.

Diğer cevapların çoğu, sizin için değeri değiştirmeyecek başka bir değişken oluşturarak çalışmanıza yardımcı olur.

Sadece açıklık için bir açıklama ekleyeceğimi düşündüm. Bir çözüm için, şahsen ben Harto'yla giderdim, çünkü cevapları burada yapmanın en açıklayıcı yolu. Yayınlanan kodlardan herhangi biri çalışır, ancak neden yeni bir değişken (Freddy ve 1800'ler) bildirdiğimi veya garip gömülü kapatma sözdizimine (apphacker) sahip olduğumu açıklamak için bir yorum yığını yazmak zorunda kaldığım için bir kapanış fabrikasını tercih ederim.


77
2018-04-15 06:48





Anlamanız gereken şey, Javascript'teki değişkenlerin kapsamı işlevine dayanmaktadır. Bu, blok kapsamına sahip olduğunuz ve sadece değişkenin içerisine bir kopyasını kopyalayacağınız c # ifadesinden önemli bir farktır.

Şu anda işlev kapsamına sahip olduğundan, apphacker'ın cevabı gibi işlevi döndüren bir işlevi sarmalamak, hile yapar.

Ayrıca, blok kapsam kuralının kullanılmasına izin veren, var yerine bir anahtar kelime de bulunur. Bu durumda, içinde bir değişken tanımlamak hile yapardı. Bununla birlikte, izin verilen anahtar kelime uyumluluk nedeniyle pratik bir çözüm değildir.

var funcs = {};
for (var i = 0; i < 3; i++) {
    let index = i;          //add this
    funcs[i] = function() {            
        console.log("My value: " + index); //change to the copy
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        
}

61
2018-04-15 06:25



Hangi tarayıcınız? Dediğim gibi, uyumluluk sorunları var, bununla birlikte IE'de desteklenebileceğini düşünmüyor gibi ciddi uyumluluk sorunları var. - eglasius
@nickf evet, bu referansı kontrol edin: developer.mozilla.org/En/New_in_JavaScript_1.7 ... tanım tanımları bölümüne bakın, bir döngü içinde onclick örneği var - eglasius
@nickf hmm, aslında açıkça sürümü belirtmeniz gerekir: <script type = "application / javascript; version = 1.7" /> ... IE kısıtlaması nedeniyle aslında hiçbir yerde kullanmadım, sadece pratik :( - eglasius
Ayrıca bakınız Hangi tarayıcılar şu anda javascript'in 'let' anahtar sözcüğünü destekliyor? - rds
Kullanmayın o zaman - regisbsb


İşte, teknikte bir başka varyasyon olan, Bjorn'un (apphacker) benzeri, değişken değerini bazen bir parametre olarak iletmekten ziyade, işlevin içinde atayabiliyorsunuz.

for (var i = 0; i < 3; i++) {
    funcs[i] = (function() {
        var index = i;
        return function() {
            console.log("My value: " + index);
        }
    })();
}

Kullandığınız her türlü tekniği unutmayın. index değişken, iç işlevin döndürülen kopyasına bağlı bir tür statik değişken olur. Yani, değerindeki değişiklikler, çağrılar arasında korunur. Çok kullanışlı olabilir.


49
2017-08-07 08:45



Teşekkürler ve çözümünüz çalışıyor. Ama bunun neden işe yaradığını sormak istiyorum ama var çizgi ve return çizgi işe yaramaz mı? Teşekkürler! - midnite
@midnite Değiştirdiyseniz var ve return Daha sonra değişken içsel işlev döndürmeden önce atanmazdı. - Boann


Bu, JavaScript'teki kapakların kullanılmasıyla ilgili yaygın hatayı açıklar.

Bir işlev yeni bir ortam tanımlar

Düşünmek:

function makeCounter()
{
  var obj = {counter: 0};
  return {
    inc: function(){obj.counter ++;},
    get: function(){return obj.counter;}
  };
}

counter1 = makeCounter();
counter2 = makeCounter();

counter1.inc();

alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0

Her zaman için makeCounter çağrıldı {counter: 0} Yeni bir nesnenin oluşturulmasını sağlar. Ayrıca, yeni bir kopyası obj  yeni nesneyi referans almak için oluşturulmuştur. Böylece, counter1 ve counter2 birbirinden bağımsızdır.

Döngüler halinde kapanışlar

Bir döngüde bir kapatma kullanmak zor.

Düşünmek:

var counters = [];

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = {
      inc: function(){obj.counter++;},
      get: function(){return obj.counter;}
    }; 
  }
}

makeCounters(2);

counters[0].inc();

alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1

Dikkat edin counters[0] ve counters[1] Hangi değil bağımsız. Aslında aynı şekilde çalışırlar. obj!

Bunun nedeni sadece bir kopyası var obj Belki de performans nedenleriyle, döngünün tüm yinelemeleri boyunca paylaşılır. Buna rağmen {counter: 0} her yinelemede yeni bir nesne oluşturur, aynı kopyasını obj sadece bir en yeni nesneye referans.

Çözüm başka bir yardımcı işlev kullanmaktır:

function makeHelper(obj)
{
  return {
    inc: function(){obj.counter++;},
    get: function(){return obj.counter;}
  }; 
}

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = makeHelper(obj);
  }
}

Bu, işlev kapsamındaki yerel değişkenlerin yanı sıra işlev bağımsız değişkenleri de ayrıştırıldığı için çalışır. girişte yeni kopyalar.

Detaylı bir tartışma için lütfen bkz. JavaScript kapatma tuzakları ve kullanımı


45
2018-04-20 09:59





En basit çözüm,

Kullanmak yerine:

var funcs = [];
for(var i =0; i<3; i++){
    funcs[i] = function(){
        alert(i);
    }
}

for(var j =0; j<3; j++){
    funcs[j]();
}

3 kere "2" uyarır. Bunun nedeni, döngü için oluşturulan anonim işlevlerin aynı kapanışa ve bu kapama değerine sahip olmasıdır. i aynı. Paylaşılan kapanmayı önlemek için bunu kullanın:

var funcs = [];
for(var new_i =0; new_i<3; new_i++){
    (function(i){
        funcs[i] = function(){
            alert(i);
        }
    })(new_i);
}

for(var j =0; j<3; j++){
    funcs[j]();
}

Bunun arkasındaki fikir, for döngüsünün tüm vücudunu bir araya getirmektir. Hayatta (Hemen Çağrılı İşlev İfadesi) ve geçiş new_i bir parametre olarak ve onu yakalama i. Anonim işlev hemen yürütüldüğü için, i anonim işlev içinde tanımlanan her işlev için değer farklıdır.

Bu çözüm, bu sorundan muzdarip orijinal kodda minimal değişiklikler gerektireceğinden, böyle bir soruna uyuyor gibi görünüyor. Aslında, bu tasarım gereği, bir sorun olmamalı!


41
2018-06-25 14:21



Bir kitapta benzer bir şeyi bir kez okuyun. Ben de bunu tercih ederim, çünkü mevcut kodunuza dokunmak zorunda kalmazsınız ve bunu neden yaptığınız anlaşılır hale gelir, bir kez kendi kendini çağıran fonksiyon modelini öğrendiniz: yeni yaratılanda bu değişkeni yakalamak kapsamı. - DanMan
@DanMan Teşekkürler. Kendini anonim işlevler çağırmak javascript'in blok seviyesi değişken kapsamının eksikliğini ele almak için çok iyi bir yoldur. - Kemal Dağ
Kendini çağıran veya kendi kendine çağıran bu teknik için uygun bir terim değildir, Hayatta (Hemen Çağrılı İşlev İfadesi) daha doğrudur. Ref: benalman.com/news/2010/11/... - jherax