Lebih
JavaScript penutupan di dalam loop – contoh praktis sederhana
var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
Output ini:
Saya nilai: 3 Saya nilai: 3 Saya nilai: 3
Sedangkan saya'a seperti itu untuk output:
Saya nilai: 0 Saya nilai: 1 Saya nilai: 2
Masalah yang sama terjadi ketika keterlambatan dalam menjalankan fungsi ini disebabkan oleh penggunaan pendengar event:
var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
// as event listeners
buttons[i].addEventListener("click", function() {
// each should log its value.
console.log("My value: " + i);
});
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>
... atau asynchronous kode, misalnya menggunakan janji-Janji:
// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for (var i = 0; i < 3; i++) {
// Log `i` as soon as each promise resolves.
wait(i * 100).then(() => console.log(i));
}
Apa solusi untuk masalah dasar?
2680
44
Nah, masalahnya adalah bahwa variabel
i
, dalam setiap fungsi anonim, terikat sama variabel di luar fungsi.Solusi klasik: Penutupan
Apa yang ingin anda lakukan adalah mengikat variabel dalam fungsi masing-masing yang terpisah, tidak berubah nilai di luar fungsi:
Karena tidak ada blok lingkup dalam JavaScript - satunya fungsi ruang lingkup - oleh pembungkus fungsi penciptaan dalam sebuah fungsi baru, anda memastikan bahwa nilai dari "aku" tetap seperti yang anda inginkan.
2015 Solusi: forEach
Dengan relatif ketersediaan luas dari
Array.prototipe.forEach
fungsi (di 2015), it's dicatat bahwa dalam situasi-situasi yang melibatkan iterasi terutama lebih dari sebuah array nilai,.forEach()
menyediakan bersih, cara alami untuk mendapatkan area penutupan untuk setiap iterasi. Artinya, dengan asumsi anda've mendapat beberapa jenis array yang berisi nilai-nilai (DOM referensi, benda-benda, apa pun), dan masalah yang timbul dari pengaturan callback spesifik untuk masing-masing elemen, anda dapat melakukan ini:Idenya adalah bahwa setiap doa fungsi callback digunakan dengan
.forEach
loop akan sendiri penutupan. Parameter yang dilewatkan ke handler itu adalah elemen array tertentu untuk tertentu langkah iterasi. Jika itu's digunakan dalam asynchronous callback, itu tidak't bertabrakan dengan yang lain callback didirikan pada langkah-langkah lain dari iterasi.Jika anda kebetulan bekerja di jQuery, para
$.masing-masing()
fungsi memberi anda kemampuan yang mirip.ES6 solusi:
membiarkan
ECMAScript 6 (ES6) memperkenalkan new
kita
danconst
kata kunci yang diperiksa berbeda darivar
-berdasarkan variabel. Misalnya, dalam satu lingkaran denganmembiarkan
-berdasarkan indeks, masing-masing iterasi melalui loop akan memiliki nilai baru dariaku
di mana masing-masing nilai scoped di dalam loop, sehingga kode anda akan bekerja seperti yang anda harapkan. Ada banyak sumber daya, tapi saya'd merekomendasikan 2ality's blok-scoping post sebagai sumber informasi.Hati-hati, meskipun, bahwa IE9-IE11 dan Tepi sebelum Edge 14 dukungan
membiarkan
tapi mendapatkan di atas salah (mereka don't membuat yang baruaku
setiap waktu, sehingga semua fungsi di atas akan log 3 seperti mereka akan jika kita menggunakanvar
). Edge 14 akhirnya mendapatkannya benar.Coba:
Edit (2014):
Secara pribadi saya pikir @Aust's lebih baru jawaban tentang menggunakan
.mengikat
adalah cara terbaik untuk melakukan hal semacam ini sekarang. Ada's juga lo-dash/underscore's_.sebagian
ketika anda don't perlu atau ingin main-main denganmengikat
'sthisArg
.Cara lain yang belum't telah disebutkan namun penggunaan dari
Fungsi.prototipe.mengikat
UPDATE
Seperti yang ditunjukkan oleh @juling dan @mekdev, anda mendapatkan kinerja yang lebih baik dengan membuat fungsi di luar lingkaran pertama dan kemudian mengikat hasil dalam loop.
Menggunakan Segera-Dipanggil Fungsi Ekspresi, yang paling sederhana dan paling mudah dibaca cara untuk menyertakan indeks variabel:
Ini mengirimkan iterator
aku
menjadi anonim fungsi yang kita definisikan sebagaiindex
. Hal ini menciptakan penutupan, dimana variabelaku
akan disimpan untuk kemudian digunakan dalam setiap asynchronous fungsi dalam HIDUP.Sedikit terlambat, tapi saya menjelajahi masalah ini hari ini dan melihat bahwa banyak jawaban don't benar-benar membahas bagaimana Javascript memperlakukan lingkup, yang pada dasarnya adalah apa yang ini bermuara.
Sehingga banyak yang lain disebutkan, masalahnya adalah bahwa fungsi batin adalah referensi yang sama
aku
variabel. Jadi mengapa don't kita hanya membuat variabel lokal baru setiap iterasi, dan memiliki fungsi batin referensi yang sebaliknya?Sama seperti sebelumnya, di mana masing-masing dalam fungsi yang ditampilkan nilai terakhir ditugaskan untuk
aku
, sekarang masing-masing dalam fungsi output nilai terakhir ditugaskan untukilocal
. Tapi seharusnya't setiap iterasi akan's sendiriilocal
?Ternyata, yang's masalah. Setiap iterasi adalah berbagi ruang lingkup yang sama, sehingga setiap iterasi pertama adalah hanya timpa
ilocal
. Dari MDN:Menegaskan untuk penekanan:
Kita dapat melihat hal ini dengan memeriksa
ilocal
sebelum kita menyatakan hal itu dalam setiap iterasi:Ini adalah persis mengapa bug ini begitu rumit. Meskipun anda redeclaring variabel, Javascript tidak't melempar kesalahan, dan JSLint won't bahkan melemparkan peringatan. Ini juga mengapa cara terbaik untuk memecahkan masalah ini adalah untuk mengambil keuntungan dari penutupan, yang pada dasarnya adalah gagasan bahwa dalam Javascript, dalam fungsi-fungsi yang memiliki akses ke luar variabel karena dalam lingkup "melampirkan" luar lingkup.
Ini juga berarti bahwa batin fungsi "terus ke" luar variabel dan menjaga mereka hidup, bahkan jika bagian luar berfungsi kembali. Untuk memanfaatkan ini, kami membuat dan memanggil fungsi wrapper murni untuk membuat ruang lingkup, menyatakan
ilocal
di new lingkup, dan kembali dalam fungsi yang menggunakanilocal
(penjelasan lebih lanjut di bawah ini):Membuat fungsi batin di dalam pembungkus fungsi memberikan batin fungsi lingkungan pribadi yang hanya bisa diakses, "penutupan". Dengan demikian, setiap kali kita memanggil fungsi wrapper kita membuat batin baru dengan fungsi it's sendiri terpisah lingkungan, memastikan bahwa
ilocal
variabel don't bertabrakan dan menimpa satu sama lain. Beberapa optimasi kecil memberikan jawaban akhir yang lainnya SEHINGGA pengguna memberikan:Update
Dengan ES6 sekarang mainstream, sekarang kita bisa menggunakan
membiarkan
kata kunci untuk membuat blok-scoped variabel:Lihatlah betapa mudahnya sekarang! Untuk informasi lebih lanjut, lihat jawaban, yang mana info saya ini didasarkan dari.
Dengan ES6 sekarang didukung secara luas, jawaban terbaik untuk pertanyaan ini telah berubah. ES6 menyediakan
kita
danconst
kata kunci untuk ini tepat keadaan. Bukan main-main dengan penutupan, kita hanya dapat menggunakankita
untuk mengatur loop lingkup variabel seperti ini:val
akan kemudian arahkan ke objek yang lebih spesifik tertentu yang berubah dari loop, dan akan mengembalikan nilai yang benar tanpa tambahan penutupan notasi. Ini jelas secara signifikan menyederhanakan masalah ini.const
mirip denganmembiarkan
dengan tambahan pembatasan bahwa variabel nama dapat't akan rebound untuk referensi baru setelah tugas awal.Browser mendukung adalah sekarang di sini untuk mereka menargetkan versi terbaru dari browser.
const
/membiarkan
yang saat ini didukung dalam terbaru Firefox, Safari, Edge dan Chrome. Hal ini juga didukung dalam Node, dan anda dapat menggunakannya di mana saja dengan mengambil keuntungan dari membangun alat-alat seperti Babel. Anda dapat melihat contoh kerja berikut ini: http://jsfiddle.net/ben336/rbU4t/2/Dokumen berikut ini:
Hati-hati, meskipun, bahwa IE9-IE11 dan Tepi sebelum Edge 14 dukungan
membiarkan
tapi mendapatkan di atas salah (mereka don't membuat yang baruaku
setiap waktu, sehingga semua fungsi di atas akan log 3 seperti mereka akan jika kita menggunakanvar
). Edge 14 akhirnya mendapatkannya benar.Cara lain untuk mengatakan itu adalah bahwa
aku
dalam fungsi anda terikat pada waktu melaksanakan fungsi, bukan waktu untuk membuat fungsi.Ketika anda membuat penutupan,
aku
adalah referensi ke variabel yang didefinisikan di luar ruang lingkup, bukan copy dari itu karena itu ketika anda membuat penutupan. Itu akan dievaluasi pada saat eksekusi.Sebagian besar jawaban yang lain menyediakan cara untuk bekerja di sekitar dengan menciptakan variabel lain yang tidak't perubahan nilai untuk anda.
Hanya pikir saya'd menambahkan penjelasan untuk kejelasan. Untuk solusi, secara pribadi, saya'd pergi dengan Harto's karena ini adalah yang paling jelas cara melakukannya dari jawaban-jawaban berikut ini. Setiap kode yang dikirim akan bekerja, tapi saya'a memilih untuk penutupan pabrik lebih memiliki untuk menulis tumpukan komentar untuk menjelaskan mengapa saya'm mendeklarasikan sebuah variabel baru(Freddy dan 1800's) atau aneh tertanam penutupan sintaks(apphacker).
Apa yang perlu anda mengerti adalah lingkup dari variabel-variabel di javascript didasarkan pada fungsi. Ini adalah sebuah perbedaan penting dari c# di mana anda memiliki blok ruang lingkup, dan hanya menyalin variabel satu di dalam yang akan bekerja.
Membungkusnya dalam sebuah fungsi yang mengevaluasi kembali fungsi seperti apphacker's jawaban akan melakukan trik, sebagai variabel sekarang memiliki fungsi ruang lingkup.
Ada juga yang membiarkan kata kunci bukan var, yang akan memungkinkan menggunakan blok lingkup aturan. Dalam hal mendefinisikan variabel dalam untuk akan melakukan trik. Yang mengatakan, biarkan kata kunci isn't solusi praktis karena kompatibilitas.
Berikut ini's variasi lain pada teknik, mirip Bjorn's (apphacker), yang memungkinkan anda menetapkan nilai variabel di dalam fungsi daripada lewat itu sebagai parameter, yang mungkin lebih jelas kadang-kadang:
Perhatikan bahwa apapun teknik yang anda gunakan,
index
variabel menjadi semacam variabel statis, terikat untuk kembali salinan dalam fungsi. I. e., perubahan nilai yang diawetkan antara panggilan. Hal ini dapat sangat berguna.Ini menggambarkan kesalahan umum dengan menggunakan penutupan di JavaScript.
Fungsi mendefinisikan lingkungan baru
Pertimbangkan:
Untuk setiap waktu
makeCounter
dipanggil,{counter: 0}
menghasilkan sebuah objek baru diciptakan. Juga, salinan baru dariobj
dibuat juga untuk referensi objek baru. Dengan demikian,counter1
dancounter2
yang independen satu sama lain.Penutupan di loop
Menggunakan penutupan di lingkaran rumit.
Pertimbangkan:
Perhatikan bahwa
counter[0]
dancounter[1]
adalah tidak independen. Pada kenyataannya, mereka beroperasi pada saat yang samaobj
!Hal ini karena hanya ada satu salinan
obj
bersama di semua iterasi dari loop, mungkin untuk alasan kinerja. Meskipun{counter: 0}
menciptakan objek baru dalam setiap iterasi, salinan yang sama dariobj
hanya akan mendapatkan diperbarui dengan referensi terbaru objek.Solusi lain adalah dengan menggunakan fungsi pembantu:
Ini bekerja karena variabel lokal dalam fungsi ruang lingkup secara langsung, serta fungsi argumen variabel, yang dialokasikan salinan baru pada saat masuk.
Untuk pembahasan yang lebih rinci, silakan lihat JavaScript penutupan perangkap dan penggunaan
Solusi yang paling sederhana akan,
Alih-alih menggunakan:
coba ini lebih pendek
tidak ada array
tidak ada tambahan untuk loop
[http://jsfiddle.net/7P6EN/][1]
Berikut ini's solusi sederhana yang menggunakan
forEach
(bekerja kembali ke IE9):Cetakan:
Masalah utama dengan kode yang ditampilkan oleh OP adalah bahwa
aku
adalah tidak pernah membaca sampai putaran kedua. Untuk menunjukkan, bayangkan melihat kesalahan dalam kodeKesalahan yang sebenarnya tidak terjadi sampai
funcs[someIndex]
dieksekusi()
. Menggunakan logika yang sama, itu harus jelas bahwa nilai dariaku
ini juga tidak dikumpulkan sampai saat ini baik. Setelah asli loop selesai,i++
membawaaku
nilai3
yang mengakibatkan kondisii < 3
gagal dan loop berakhir. Pada titik ini,aku
ini3
dan jadi ketikafuncs[someIndex]()
digunakan, danaku
ini dievaluasi, itu adalah 3 - setiap kali.Untuk bisa melewati ini, anda harus mengevaluasi
aku
seperti yang ditemui. Perhatikan bahwa hal ini telah terjadi dalam bentukfuncs[i]
(mana ada 3 indeks unik). Ada beberapa cara untuk menangkap nilai ini. Salah satunya adalah untuk lulus sebagai parameter ke fungsi yang ditampilkan dalam beberapa cara sudah di sini.Pilihan lain adalah untuk membangun sebuah fungsi objek yang akan mampu untuk menutup lebih dari satu variabel. Yang dapat dicapai thusly
[
Demo jsFiddle
](http://jsfiddle.net/QcUjH/)Fungsi JavaScript "tutup" lingkup mereka memiliki akses ke atas deklarasi, dan mempertahankan akses ke cakupan bahkan sebagai variabel dalam lingkup perubahan.
Masing-masing fungsi dalam array di atas menutup atas lingkup global (global, hanya karena hal itu terjadi untuk menjadi lingkup mereka're dinyatakan dalam).
Kemudian fungsi-fungsi yang dipanggil logging yang paling saat ini nilai dari
aku
dalam lingkup global. Yang's magic, dan frustrasi, dari penutupan."Fungsi JavaScript dekat lebih dari lingkup mereka menyatakan dalam, dan mempertahankan akses ke cakupan bahkan sebagai nilai-nilai variabel dalam lingkup perubahan."
Menggunakan
kita
bukanvar
memecahkan ini dengan menciptakan ruang lingkup setiap kaliuntuk
loop berjalan, menciptakan dipisahkan lingkup untuk masing-masing fungsi untuk menutup di atas. Berbagai teknik lain melakukan hal yang sama dengan fungsi tambahan.(
membiarkan
membuat variabel blok scoped. Blok dilambangkan dengan kurung kurawal, tetapi dalam kasus lingkaran untuk inisialisasi variabel,aku
dalam kasus kami, dianggap dinyatakan dalam kawat gigi.)Setelah membaca melalui berbagai solusi, I'd ingin menambahkan bahwa alasan mereka solusi bekerja adalah untuk mengandalkan konsep lingkup rantai. It's the way JavaScript menyelesaikan variabel selama eksekusi.
var
danargumen
.jendela
.Di awal kode:
Ketika
funcs
akan dilaksanakan, lingkup rantai akan menjadifungsi batin -> global
. Karena variabeli
tidak ditemukan difungsi batin
(tidak dinyatakan menggunakanvar
atau dilewatkan sebagai argumen), terus mencari, sampai nilaiaku
ini akhirnya ditemukan di lingkup global yang merupakanjendela.aku
.Dengan membungkusnya di luar fungsi baik secara eksplisit mendefinisikan fungsi pembantu seperti harto atau tidak menggunakan fungsi anonim seperti Bjorn lakukan:
Ketika
funcs
akan dieksekusi, kini cakupan jaringan akanfungsi batin -> fungsi luar
. Kali iniaku
dapat ditemukan di luar fungsi's lingkup yang dilaksanakan 3 kali dalam loop for, setiap waktu memiliki nilaiaku
terikat dengan benar. Itu tidak't menggunakan nilai darijendela.saya
ketika batin dieksekusi.Detail lebih lanjut dapat ditemukan di sini
Ini termasuk kesalahan umum dalam menciptakan penutupan dalam lingkaran seperti apa yang kita miliki di sini, serta mengapa kita perlu penutupan dan pertimbangan kinerja.
Dengan fitur-fitur baru dari ES6 blok tingkat scoping dikelola:
Kode di OP's pertanyaan diganti dengan
membiarkan
bukanvar
.I'm terkejut tidak ada namun telah disarankan menggunakan
forEach
fungsi untuk lebih baik menghindari (re)menggunakan variabel lokal. Pada kenyataannya, saya'm tidak menggunakanuntuk(var i ...)
sekali lagi untuk alasan ini.// diedit menggunakan
forEach
bukan peta.Pertanyaan ini benar-benar menunjukkan sejarah JavaScript! Sekarang kita dapat menghindari blok scoping dengan panah fungsi dan menangani loop langsung dari DOM node Objek dengan menggunakan metode.
const tombol = dokumen.getElementsByTagName("tombol"); Objek .tombol(tombol) .peta(i => tombol[saya].addEventListener('klik', () => console.log(i)));
Pertama-tama, memahami apa's salah dengan kode ini:
Di sini ketika
funcs[]
array diinisialisasi,aku
ini menjadi bertambah, yangfuncs
array diinisialisasi dan ukuranfunc
array menjadi 3, sehinggai = 3,
. Sekarang ketikafuncs[j]()
disebut, itu lagi-lagi menggunakan variabeli
, yang telah bertambah 3.Sekarang untuk memecahkan masalah ini, kita memiliki banyak pilihan. Di bawah ini adalah dua dari mereka:
saya
denganmembiarkan
atau menginisialisasi sebuah variabel baruindex
denganmembiarkan
dan membuat ia sama denganaku
. Jadi ketika panggilan sedang dibuat,index
akan digunakan dan ruang lingkup akan berakhir setelah inisialisasi. Dan untuk memanggil,index
akan dimulai lagi:var funcs = []; for (var i = 0; i < 3; i++) { biarkan index = i; funcs[i] = function() { konsol.log("Saya nilai: " + index); }; } for (var j = 0; j < 3; j++) { funcs[j](); }
tempFunc
yang mengembalikan fungsi sebenarnya:var funcs = []; fungsi tempFunc(i){ return function(){ konsol.log("Saya nilai: " + i); }; } for (var i = 0; i < 3; i++) { funcs[i] = tempFunc(i); } for (var j = 0; j < 3; j++) { funcs[j](); }