Charles Hong 已發佈 2019-10-27

JavaScript 的 Closure 概念

認識一級函數

一級函數 (First-class function) 是指程式語言把函數當成第一類公民 (First-class citizen),函數可以像一般變數的操作,例如作為別的函數的參數,函數的回傳值,賦值給變數或存在資料結構(物件或陣列)中。

而 JavaScript 是具有一級函數的程式語言,所以函數 (Function) 也能和其他型別 (Object, String, Number, Boolean) 做一樣的事,也就是會有以下的特性:

  • 指定給變數
  • 當成參數傳入另一個函數中
  • 能用實體語法立刻創造
  • 可以從一個函數中回傳
  • 函數也是物件,一樣有屬性

Closure 的特性

  • 程式範例
// 建立 greeting 函數,然後在 greeting 函數內回傳一個匿名函數
function greeting(word) {
    // 回傳匿名函數
    return function(name) {
        console.log(word + '! ' + name); // Hi! Charles
    }
}

// 呼叫 greeting 函數,傳入參數 'Hi',將回傳的匿名函數指定給變數 person
var person = greeting('Hi');

// 呼叫匿名函數,傳入參數 'Charles'
person('Charles');

  1. 程式開始執行時,會產生一個全域執行環境 (Global Execution Context)。
    images

  1. 當執行到這一行 var person = greeting('Hi'); 呼叫 greeting 函數,於是 greeting 函數的執行環境被建立,變數 word 被傳入儲存在執行環境的記憶體內。
    images

  1. 此時 greeting 函數執行結束後會回傳匿名函數,然後就會離開執行堆。每個執行環境都有自己的記憶體空間,雖然執行環境沒了,但記憶體空間仍然會存在。
    images

  1. 接著程式往下執行到這一行 person('Charles'); 呼叫 person 匿名函數,同樣的 person 匿名函數的執行環境被建立,變數 name 被傳入儲存在執行環境的記憶體內。
    images

  1. 最後執行匿名函數內 console.log(word + '! ' + name); 然而 JavaScript 引擎在匿名函數內找不到變數 word,所以會透過 Scope Chain 往外層環境 (outer lexical environment) 去尋找這個變數。於是在 Chrome 的 Console 會看到程式執行的結果 Hi! Charles
    images

統整以上的說明,可以知道即使外層 greeting 函數的執行環境已經離開執行堆了,但記憶體空間依舊存在,因此,內層匿名函數的執行環境仍然可以參考到外層環境的記憶體空間找到變數 word,也就是內層的執行環境把外層的變數包住了,而這樣的現象也就是所謂的閉包。

總結

  • 執行環境可以把它的外層變數包在一起,那些它應該要參考到的變數,而這個包住可以取用所有變數的現象稱為閉包 (Closure)。
  • 閉包只是 JavaScript 的特色,JavaScript 引擎會確保無論現在執行哪個函數,都能取用到應該要取用的變數。

參考資料

關於筆者

暱稱:Charles Hong

文章列表 文章列表