關於常見的一個循環和閉包的錯誤,不少資料對此都有文字解釋,但仍是難以理解。本文將以執行環境圖示的方式來對此進行更直觀的解釋,以及對此類需求進行推衍,獲得更合適的解決辦法html
function foo(){ var arr = []; for(var i = 0; i < 2; i++){ arr[i] = function(){ return i; } } return arr; } var bar = foo(); console.log(bar[0]());//2
以上代碼的運行結果是2,而不是預想的0。接下來用執行環境圖示的方法,詳解究竟是哪裏出了問題編程
執行流首先建立並進入全局執行環境,進行聲明提高過程。執行流執行到第10行,建立並進入foo()函數執行環境,並進行聲明提高。而後執行第2行,將arr賦值爲[]。而後執行第3行,給arr[0]和arr[1]都賦值爲一個匿名函數。而後執行第8行,以arr的值爲返回值退出函數。因爲此時有閉包的存在,因此foo()執行環境並不會被銷燬數組
執行流進入全局執行環境,繼續執行第10行,將函數的返回值arr賦值給bar閉包
執行流執行第11行,訪問bar的第0個元素並執行。此時,執行流建立並進入匿名函數執行環境,匿名函數中存在自由變量i,須要使用其做用域鏈匿名函數 -> foo()函數 -> 全局做用域進行查找,最終在foo()函數的做用域找到了i,而後在foo()函數的執行環境中找到了i的值2,因而給i賦值2函數
執行流接着執行第5行,以i的值2做爲返回值返回。同時銷燬匿名函數的執行環境。執行流進入全局執行環境,接着執行第11行,調用內部對象console,並找到其方法log,將bar[0]()的值2做爲參數放入該方法中,最終在控制檯顯示2spa
由此咱們看出,犯錯緣由是在循環的過程當中,並無把函數的返回值賦值給數組元素,而僅僅是把函數賦值給了數組元素。這就使得在調用匿名函數時,經過做用域找到的執行環境中儲存的變量的值已經不是循環時的瞬時索引值,而是循環執行完畢以後的索引值code
由此,能夠利用IIFE傳參和閉包來建立多個執行環境來保存循環時各個狀態的索引值。由於函數傳參是按值傳遞的,不一樣參數的函數被調用時,會建立不一樣的執行環境htm
function foo(){ var arr = []; for(var i = 0; i < 2; i++){ arr[i] = (function fn(j){ return function test(){ return j; } })(i); } return arr; } var bar = foo(); console.log(bar[0]());//0
使用IIFE仍是較爲複雜,使用塊做用域則更爲方便對象
因爲塊做用域能夠將索引值i從新綁定到了循環的每個迭代中,確保使用上一個循環迭代結束時的值從新進行賦值,至關於爲每一次索引值都建立一個執行環境blog
function foo(){ var arr = []; for(let i = 0; i < 2; i++){ arr[i] = function(){ return i; } } return arr; } var bar = foo(); console.log(bar[0]());//0
在編程中,若是實際和預期結果不符,就按照代碼順序一步一步地把執行環境圖示畫出來,會發現不少時候就是在想固然
以上