Agak terlambat ke pesta, tapi saya sedang mengeksplorasi masalah ini hari ini dan memperhatikan bahwa banyak jawaban tidak sepenuhnya membahas bagaimana Javascript memperlakukan ruang lingkup, yang pada dasarnya adalah intinya.
Jadi seperti yang banyak disebutkan, masalahnya adalah bahwa fungsi bagian dalam merujuk i
variabel yang sama . Jadi mengapa kita tidak hanya membuat variabel lokal baru setiap iterasi, dan memiliki referensi fungsi bagian dalamnya?
//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]();
}
Sama seperti sebelumnya, di mana setiap fungsi batin menghasilkan nilai terakhir yang ditetapkan i
, sekarang setiap fungsi batin hanya menampilkan nilai terakhir yang ditetapkan ilocal
. Tapi bukankah setiap iterasi memiliki itu sendiri ilocal
?
Ternyata, itulah masalahnya. Setiap iterasi berbagi ruang lingkup yang sama, sehingga setiap iterasi setelah yang pertama hanya menimpa ilocal
. Dari MDN :
Penting: JavaScript tidak memiliki cakupan blok. Variabel yang diperkenalkan dengan blok dicakup oleh fungsi atau skrip yang berisi, dan efek pengaturannya tetap ada di luar blok itu sendiri. Dengan kata lain, pernyataan blok tidak memperkenalkan ruang lingkup. Meskipun blok "standalone" adalah sintaks yang valid, Anda tidak ingin menggunakan blok standalone dalam JavaScript, karena mereka tidak melakukan apa yang Anda pikir mereka lakukan, jika Anda berpikir mereka melakukan sesuatu seperti blok seperti di C atau Java.
Diulangi untuk penekanan:
JavaScript tidak memiliki ruang lingkup blokir. Variabel yang diperkenalkan dengan blok dicakup oleh fungsi atau skrip yang berisi
Kita dapat melihat ini dengan memeriksa ilocal
sebelum menyatakannya di setiap iterasi:
//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;
}
Inilah mengapa bug ini sangat rumit. Meskipun Anda mendeklarasikan ulang variabel, Javascript tidak akan menampilkan kesalahan, dan JSLint bahkan tidak akan memberikan peringatan. Ini juga mengapa cara terbaik untuk menyelesaikan ini adalah dengan mengambil keuntungan dari penutupan, yang pada dasarnya adalah gagasan bahwa dalam Javascript, fungsi dalam memiliki akses ke variabel luar karena cakupan dalam "menyertakan" cakupan luar.
Ini juga berarti bahwa fungsi dalam "memegang" variabel luar dan membuatnya tetap hidup, bahkan jika fungsi luar kembali. Untuk memanfaatkan ini, kami membuat dan memanggil fungsi wrapper murni untuk membuat lingkup baru, mendeklarasikan ilocal
dalam lingkup baru, dan mengembalikan fungsi bagian dalam yang menggunakan ilocal
(penjelasan lebih lanjut di bawah):
//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]();
}
Menciptakan fungsi dalam di dalam fungsi pembungkus memberikan fungsi internal lingkungan pribadi yang hanya dapat diakses, sebuah "penutupan". Jadi, setiap kali kita memanggil fungsi wrapper, kita membuat fungsi internal baru dengan lingkungannya sendiri yang terpisah, memastikan bahwa ilocal
variabel tidak saling bertabrakan dan saling menimpa. Beberapa optimasi kecil memberikan jawaban akhir yang diberikan banyak pengguna SO lainnya:
//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);
};
}
Memperbarui
Dengan ES6 sekarang menjadi arus utama, kami sekarang dapat menggunakan let
kata kunci baru untuk membuat variabel blok-tertutup:
//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]();
}
Lihatlah betapa mudahnya sekarang! Untuk informasi lebih lanjut, lihat jawaban ini , yang menjadi dasar info saya.
funcs
menjadi array, jika Anda menggunakan indeks numerik? Hanya kepala.