今天突然看到群里有童鞋問這樣的一個問題
function fn(){
var arr=[];
for(var i=0;i<5;i++){
arr[i] = function(){
return i;
}
}
return arr;
}
var b = fn();
for(var i = 0;i<5;i++){
console.log(b[i]());
}
第一眼看過去,可能一些接觸js這門語言不太久的童鞋們會認為輸出的結果是0,1,2,3,4
但是執行完這段代碼之后卻發現循環出來了五次都是5,這是為什么呢?
當然一部分童鞋一眼能看出正確結果,我也相信有的童鞋雖然能知道結果,但是思路并不是特別清晰,今天就來分析下到底為什么會這樣執行
這里涉及到了作用域的問題
我們猜想的是每次循環,函數內部都會捕獲當前i的值
for(var i=0;i<5;i++){
arr[i] = function(){
return i; //0,1,2,3,4
}
}
但是根據作用域的原理,雖然循環了五次,而且函數都是在各自的循環之中定義的,but這里又要說一個但是了,強調下重點它們都被封閉在函數fn這個函數作用域之中,因此實際上只有一個i,每次循環都會給i重新賦值
注意:當fn執行的時候循環執行完成,而此時的匿名函數并沒有執行,此刻的i已經在循環完成之后變成了5,
當執行這段代碼的時候
for(var i = 0;i<5;i++){
console.log(b[i]());
}
b[i]會依次調用存儲于arr數組中的匿名函數,而此刻匿名函數才開始執行,去獲取i的值,因為i都位于fn的函數作用域下,此刻已經變成了5,所以,自然也就看到了輸出五次5
所以此時的函數都引用的是同一個i,循環會誤導你,讓你錯誤的判斷
那么,怎么樣才能避免這種情況,其實改造方法也很簡單,我們利用IIFE,也就是立即執行函數,來形成單獨的作用域
function fn(){
var arr=[];
for(var i=0;i<5;i++){
(function(i){
arr[i]=function(){
return i;
}
})(i);
}
return arr;
}
var b = fn();
for(var i = 0;i<5;i++){
console.log(b[i]());
}
這樣,在行成獨立的作用域之后,我們就可以拿到當前循環的i,因為擁有了獨立的作用域,i的值不會再相互影響
當再次調用位于arr數組中的匿名函數時,就可以找到儲存于立即執行函數作用域中的i,而此刻的i正式我們希望看到的,會輸出0,1,2,3,4
這是我的一部分理解,如果有理解偏差,歡迎各位童鞋們一起探討