× 目錄 [1]犯錯 [2]IIFE [3]let 前面的話 關於常見的一個迴圈和閉包的錯誤,很多資料對此都有文字解釋,但還是難以理解。本文將以執行環境圖示的方式來對此進行更直觀的解釋,以及對此類需求進行推衍,得到更合適的解決辦法 犯錯 以上代碼的運行結果是2,而不是預想的0。接下來用執行環境圖示的 ...
×
目錄
[1]犯錯 [2]IIFE [3]let前面的話
關於常見的一個迴圈和閉包的錯誤,很多資料對此都有文字解釋,但還是難以理解。本文將以執行環境圖示的方式來對此進行更直觀的解釋,以及對此類需求進行推衍,得到更合適的解決辦法
犯錯
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作為參數放入該方法中,最終在控制台顯示2
由此我們看出,犯錯原因是在迴圈的過程中,並沒有把函數的返回值賦值給數組元素,而僅僅是把函數賦值給了數組元素。這就使得在調用匿名函數時,通過作用域找到的執行環境中儲存的變數的值已經不是迴圈時的瞬時索引值,而是迴圈執行完畢之後的索引值
IIFE
由此,可以利用IIFE傳參和閉包來創建多個執行環境來保存迴圈時各個狀態的索引值。因為函數傳參是按值傳遞的,不同參數的函數被調用時,會創建不同的執行環境
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重新綁定到了迴圈的每一個迭代中,確保使用上一個迴圈迭代結束時的值重新進行賦值,相當於為每一次索引值都創建一個執行環境
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
最後
在編程中,如果實際和預期結果不符,就按照代碼順序一步一步地把執行環境圖示畫出來,會發現很多時候就是在想當然
以上