2016年5月25日

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


這篇文章中,讓我們來了解一下,JavaScript中很重要的closures概念。

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

我們先建立一個function greet,這個function會回傳另一個匿名函式(anonymous function);在這個匿名函式中則會用參數name和greet函式中的參數whattosay。

程式最下面的地方,我們把greet函式帶入參數"Welcome",並把greet函式內回傳的匿名函式儲存到變數guest中。接著我們才帶入匿名函式的參數"PJCHENder"。


執行的結果如下:


看起來好像相當合理,不過讓我們思考一下(建議可先閱讀:[筆記] 不同execution context的變項不會互相影響─了解function背後運作的邏輯),我們知道當一個function執行完畢後,這個function和裡面的變項即從execution stack中抽離了,也就是說,當我們在執行這個匿名函式的時候,greet函式的參數whattosay理論上已經消失了,但是我們卻還是可以正確的呼叫到whattosay這個參數,為什麼呢?

讓我們來看看這段程式背後執行的邏輯:

首先Global Execution Context被建立;由於hoisting的緣故,function greet會被儲存在記憶體中,接著開始逐行執行,首先執行到 var guest = greet('Welcome') 這段,於是function greet的execution context被建立,whattosay這個變數也被儲存在function greet的execution context中。


透過greet函式,我們建立了一個匿名函式。這時候function greet也就執行完了,並從execution stack中抽離。


這裡有一個問題,我們知道每一個execution context都有其記憶體位置,而當一個execution context抽離後,它的記憶體位置會發生什麼事呢?

一般來說,JavaScript引擎會透過一個稱做garbage collection來清除這些內容,但是當這個execution context抽離的當下,雖然execution context已經不在了,但其中的變數其實還是儲存在那個記憶體位置:


接著,會繼續回到Global Execution Context去逐行執行,這時候碰到了 guest('PJCHENder') 於是我們建立了一個給匿名函式的execution context,同時裡面帶有變數name。


當我們去執行這個匿名函式時,會讀到 console.log(whattosay+' '+name) ,當JavaScript引擎看到 whattosay 時,因為他在自己的function裡面找不到whattosay這個變數,所以會開始透過Scope Chain尋找這個變項。這時候雖然我們的greet這個function的execution context已經不在了,但其實在這個記憶體位置仍然留有參照(reference),所以在greet function裡面所建立的函式仍然可以找得到whattosay這個變數。


讓我們在統整一次,雖然greet function的execution context已經離開execution stack了,但這個execution context在記憶體中所建立的位置並沒有消失,因此,JavaScript引擎仍然可以透過Scope Chain找到這個變數。

這時候,我們可以說,這個Execution Context和其外面的變數關閉(Close)在一起,而這樣的現象就稱為closure。closure是JavaScript引擎的一種特性,並不是說你需要去創造它或執行它。透過closure這樣的特性,我們可以確保當我們在執行function的時候,JavaScript引擎能夠找到其相對應的變數,也就是說,不論某一個function是不是已經執行完畢,是不是已經抽離execution stack,JavaScript引擎仍然可以找到外面的變數


程式範例

function greet(whattosay) {

  return function(name) {
    console.log(whattosay + ' ' + name);
  }

}

var guest = greet('Welcome');
guest('PJCHENder');
→回到此系列文章目錄

0 意見:

張貼留言