作用域閉包之 循環與閉包(1)

今天突然看到群里有童鞋問這樣的一個問題

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

這是我的一部分理解,如果有理解偏差,歡迎各位童鞋們一起探討

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容