JavaScript 裡面常常有一些規則會讓人覺得摸不清頭緒,而 Closure (也稱之為閉包)正是其中的一種。而這篇文章就要來談談,究竟甚麼是 Closure,而 Closure 在 JavaScript 當中究竟扮演著甚麼樣的角色。

JavaScript 尋找變數的方法和許多程式語言有所不同,每當你定義一個物件或函式的時候,它便會產生一個新的 Scope。

(function bar() {
  var foo = 20;
  console.log(foo); \\ 20
})();

console.log(foo); \\ undefined

而 JavaScript 的變數域非常有趣,它可以往外找,但是和其他語言相同,我們並無法存取其他函式或是變數域裡面的變數。因此在上面的例子,我們無法去存取到在 bar() 裡面定義的 Foo。 然而,Closure 讓我們可以不用汙染全域的環境,卻也可以和封閉 Scope 的變數或函式進行「互動」(注意到我說的是互動,代表它的做法和直接存取還是有所差異。)

舉個例子來說,我們有個 Account 的函式,它會回傳一組 API 讓使用者可以進行操作,例如查看餘額、存款及提款等等。

var account = function() {
  var balance = 0;

  return {
    checkAccountBalance: function() {
      console.log(balance);
    },

    deposit: function(amount) {
      balance += amount;
    }

    withdraw: function(amount) {
      balance -= amount;
    }
  }
}

var myAccount = account();

myAccount.checkAccountBalance(); // 0
myAccount.deposit(20);
myAccount.checkAccountBalance(); // 20
myAccount.withdraw(10)
myAccount.checkAccountBalance(); // 10

balance 這個區域變數會完全被隔離開來,我們只能使用 account() 所給予的其他函式,而這些函式裡面的 balance 直指我們在 account() 中所定義的區域變數,因此即便我們想要試著竄改 balance 的數值:myAccount.balance = 1000000 ,這樣也是沒有意義的,因為這樣子只是在 myAccount 這個物件上多加了一個 Property,與我們真正關心的 balance 沒有關連。

利用 Closure 作為函式的模板

Closure 有另一個用處,那就是當成函式的模板,可以方便且自由地定義出功能類似但內容有些微不同的函式。


var changeCurrency = function(rate) {
  return function(amount) {
    return (amount * rate);
  }
}

var fromUSD = changeCurrency(30);
var toJPY = changeCurrency(3.7);

fromUSD(40) // 1200
toJPY(1000) // 3700

首先,changeCurrency 就是我們的模板,我們只要呼叫這個函式並加入幣值為參數,我們就可以得到一個新的函式。由於這個函式也會存取我們原本加入的 rate,因此我們呼叫這個新的函式後就可以得到換算後的幣值。而透過有意義的命名,我們可以用非常簡單的方式生產出許多相似的函式。

JavaScript 不像許多物件導向的語言,有封裝的設計,同時也沒有所謂的 Private 或 Protected 函式,但透過 Closure 的方式我們可以決定要暴露哪些 API,而這樣的 Pattern 可以讓我們的程式碼更加有組織性且更容易除錯。