JavaScript 全攻略:克服 JS 的奇怪部分 - 物件與函數 - 閉包(2)

承接上一章:

接下來看另一個例子:

function buildArrayfun(){
  var arr = [],i;
  for(i =0;i<3;i++){
    arr.push(
     function(){console.log(i);}
    );
  }
  return arr;
}

var b = buildArrayfun();

b[0]();
b[1]();
b[2]();

執行結果如下:

 

是3,3,3不是0,1,2,why?

同樣的,我們也來探究一下程式執行的過程:

1.首先,創造一個Global Execution context,並且抬升buildArrayfun與b

2.接著執行buildArrayfun,創造了execution context,並抬升變數i與arr,之後執行for迴圈,每次都會push一個function進入arr,反覆執行三次之後,i變數是3,這邊要注意一點,我跑迴圈的時候,是不會執行裡面的function(){console.log(i)}的,因為並沒有呼叫,也就是說,i不會帶入值,arr陣列裡面有三個長的一模一樣的function。

3.當buildArrayfun結束執行之後,會抽離execution conrext,但會留下變數i=3與arr[3]在記憶體之中

4.接著執行b[0]()會透過scope chain去找,最後找到i=3,所以console.log(3)印出3

5.之後b[0]()執行完畢,抽離execution context,同理b[1]()與b[2]()也是一樣的結果,這裡就不多贅述。

 

結論:

所以這邊我們得到一個結論,函數的執行與函數的宣告,其實是兩件事,函數執行的時候,就會透過closure這個現象,配合scope chain去找出對應的變數。所以這就是為什麼印出來是3,3,3而不是0,1,2。

如果要印出0,1,2這個結果,我們就必須使用IIFE(立即執行)去保留當下closure變數的結果,程式碼如下:

function buildArrayfun(){
  var arr = [],i;
  for(i =0;i<3;i++){
    arr.push(
      (function(j){
        return function(){
          console.log(j)
        }
      })(i)
    );
  }
  return arr;
}

var b = buildArrayfun();

b[0]();
b[1]();
b[2]();

如此每次迴圈在跑的時候,都會去執行return function(){console.log(j))這行,讓之後j能找到當下迴圈執行closure所對應的變數i。

題外話,如果你想看function execution context 中的closure,找出變數對應的值,可以在執行該function之後進入debugger模式,F12會有顯示。

參考資料

https://www.udemy.com/javascriptjs/learn/v4/overview