2016年5月26日

[筆記] 談談JavaScript中closure的概念 -- Part 2


在上一次的筆記中([筆記] 談談JavaScript中的closure的概念),我們說明了closure的概念,這篇筆記我們則會看更進一步的例子。

讓我們先來看一下這段程式碼:

我們先建立一個函式,名稱為buildFunctions,接著在函式裡面建立陣列arr,然後我們建立一個迴圈,迴圈每執行一次,就會把一個function放入陣列中,函式的最後,則是回傳arr這個陣列。

程式的最後,我們利用 fs[0]( ) 來執行每個陣列中的函式,猜猜看結果會得到什麼呢?

你覺得會是0, 1, 2嗎?


答案是3, 3, 3。為什麼呢?


讓我們來進一步了解這個程式執行的過程發生了什麼事?

首先,在程式碼執行的開始,buildFunctions和變數fs會由於hoisting的緣故,先被建立在記憶體中,接著在開始逐行執行程式,這時候的Global Execution Context如下:


接著會執行到 var fs = buildFunctions( ) ,於是產生了bulidFunctions的execution context,在裡面的迴圈執行完之後,會產生一個變數 i 和陣列 arr。


當我們執行到函式中的 return arr 時,變數 i 和陣列arr的值會是什麼呢?

我們來思考一下,當buildFunctions這個函式開始執行到for迴圈時,一開始 i = 0,這時候會把陣列的內容 function( ){console.log(i)} 儲存到陣列中,但要注意的是這時候這個被儲到陣列中的函式並沒有執行(invoke),而是只是儲存在裡面而已,因為它沒有透過括號 ( ) 來執行;然後 i 會繼續累加,當 i 累加到3的時候,因為不符合 i < 3 所以會跳出迴圈。這也就是為什麼 i = 3。

當我們透過 console.log( fs[0] ) 來看陣列的內容,同樣地會發現三個陣列的內容都是一樣的function:


當bulidFunctions這個函式執行結束後,便會抽離execution stack,而buildFunctions的execution context也就消失了。但由上一堂課我們談到的closure的概念可以知道,雖然execution context消失了,但是這個execution context內所儲存的變項仍然留有參照,仍然可以透過Scope Chain找到它們。


因此,當我們回到Global Execution Context繼續逐行執行程式的時候,這時候執行到了 fs[0]( ) ,這時候新的Execution Context被建立,由於在這個function中會使用到 i ,可是在這個function裡面找不到變數 i ,於是它只好往它的Outer Environment去尋找,於是在它的Lexical Environment外找到了 i = 3,因此就會呼叫出3的結果。


執行完之後,fs[0]( )的execution context就結束了,再次從execution stack中抽離。以此類推,繼續進行fs[1]( )和fs[2]( )的execution context,它們和fs[0]( )的execution context有同樣的outer environment reference,因為這個function一開始建立的時候,是在同一個位置。


看看另一個範例


讓我們在看一個和closure有關的例子,看看你是不是對closure的概念更了解了:


我們要知道,當我們在執行buildFunctions2的時候,其實並還沒有執行裡面number這個function,number這個function只是被儲存在記憶體中而已。雖然bulidFunctions2已經先執行完,離開execution stack了,但execution context裡面的 i 仍然被存在記憶體中,因此,當我後來在執行 output 的時候,仍然可以透過scope chain找到 i 這個變數的值。

範例程式

function buildFunctions(){
 
 var arr = [];

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


var fs = buildFunctions();
fs[0]();
fs[1]();
fs[2]();

/*-------------------------*/

function buildFunctions2(){

 i = 3;

 function number(){
  console.log(i);
 }
 
 return number;
}

var output = buildFunctions2();
console.log(output);
output();
→回到此系列文章目錄

0 意見:

張貼留言