文章出處

前面的話

  關于常見的一個循環閉包的錯誤,很多資料對此都有文字解釋,但還是難以理解。本文將以執行環境圖示的方式來對此進行更直觀的解釋,以及對此類需求進行推衍,得到更合適的解決辦法

 

犯錯

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    

 

最后

  在編程中,如果實際和預期結果不符,就按照代碼順序一步一步地把執行環境圖示畫出來,會發現很多時候就是在想當然

  以上


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()