Đến bữa tiệc muộn một chút, nhưng tôi đã khám phá vấn đề này ngày hôm nay và nhận thấy rằng nhiều câu trả lời không giải quyết hoàn toàn cách Javascript xử lý phạm vi, mà về cơ bản là điều này thực hiện.
Vì vậy, như nhiều người khác đã đề cập, vấn đề là hàm bên trong đang tham chiếu cùng một i
biến. Vậy tại sao chúng ta không tạo một biến cục bộ mới mỗi lần lặp và thay vào đó có tham chiếu hàm bên trong?
//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]();
}
Cũng giống như trước đây, nơi mỗi hàm bên trong xuất ra giá trị cuối cùng được gán cho i
, bây giờ mỗi hàm bên trong chỉ xuất ra giá trị cuối cùng được gán ilocal
. Nhưng không phải mỗi lần lặp đều có cái riêng ilocal
?
Hóa ra, đó là vấn đề. Mỗi lần lặp được chia sẻ cùng một phạm vi, vì vậy mỗi lần lặp sau lần đầu tiên chỉ là ghi đè ilocal
. Từ MDN :
Quan trọng: JavaScript không có phạm vi khối. Các biến được giới thiệu với một khối được đặt trong phạm vi hàm hoặc tập lệnh chứa và các hiệu ứng của việc đặt chúng vẫn tồn tại ngoài chính khối đó. Nói cách khác, các câu lệnh chặn không đưa ra một phạm vi. Mặc dù các khối "độc lập" là cú pháp hợp lệ, bạn không muốn sử dụng các khối độc lập trong JavaScript, vì chúng không làm những gì bạn nghĩ chúng làm, nếu bạn nghĩ chúng làm bất cứ điều gì như các khối như vậy trong C hoặc Java.
Lặp lại để nhấn mạnh:
JavaScript không có phạm vi khối. Các biến được giới thiệu với một khối được đặt trong phạm vi hàm hoặc tập lệnh chứa
Chúng ta có thể thấy điều này bằng cách kiểm tra ilocal
trước khi chúng ta khai báo nó trong mỗi lần lặp:
//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;
}
Đây chính xác là lý do tại sao lỗi này rất khó khăn. Mặc dù bạn đang khai báo lại một biến, Javascript sẽ không gây ra lỗi và JSLint thậm chí sẽ không đưa ra cảnh báo. Đây cũng là lý do tại sao cách tốt nhất để giải quyết vấn đề này là tận dụng các bao đóng, về cơ bản là ý tưởng rằng trong Javascript, các hàm bên trong có quyền truy cập vào các biến bên ngoài vì phạm vi bên trong "bao quanh" phạm vi bên ngoài.
Điều này cũng có nghĩa là các hàm bên trong "giữ" các biến ngoài và giữ cho chúng tồn tại, ngay cả khi hàm ngoài trả về. Để sử dụng điều này, chúng tôi tạo và gọi một hàm bao bọc hoàn toàn để tạo một phạm vi mới, khai báo ilocal
trong phạm vi mới và trả về một hàm bên trong sử dụng ilocal
(giải thích thêm bên dưới):
//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]();
}
Tạo chức năng bên trong bên trong chức năng bao bọc cung cấp cho chức năng bên trong một môi trường riêng tư mà chỉ có nó có thể truy cập, "đóng cửa". Do đó, mỗi khi chúng ta gọi hàm bao bọc, chúng ta sẽ tạo một hàm bên trong mới với môi trường riêng biệt của nó, đảm bảo các ilocal
biến không va chạm và ghi đè lên nhau. Một vài tối ưu hóa nhỏ đưa ra câu trả lời cuối cùng mà nhiều người dùng SO khác đưa ra:
//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);
};
}
Cập nhật
Với ES6 bây giờ là chính, bây giờ chúng ta có thể sử dụng let
từ khóa mới để tạo các biến trong phạm vi khối:
//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]();
}
Nhìn nó dễ quá bây giờ! Để biết thêm thông tin hãy xem câu trả lời này , thông tin của tôi dựa trên.
funcs
là một mảng, nếu bạn đang sử dụng các chỉ số số? Chỉ cần một cái đầu lên.