簡化來說,Closure 是擁有閒置變數(Free variable)的運算式。閒置變數真正扮演的角色依當時參考的語彙環境(Lexical environment)而定。支援閉包的程式語言通常具有一級函式(First-class function)。建立函式不等於建立閉包。如果函式的閒置變數與當時語彙環境綁定,該函式才稱為閉包。
那麼何為閒置變數?閒置變數是指對於函式而言,既非區域變數也非參數的變數,像區域變數或參數,其作用範圍基本上在被定義的函式範圍中。它是被綁定變數(Bound variable)。
有沒有白話一點的寫法?唔!…就是… 舉個例子來說:
function init() {
var local = 10;
setInterval(function() {
alert(new Date() + ': ' + local);
}, 3000);
}
window.onload = init;
以上程式片段中,單看粗體字部份,local
並沒有意義,對粗體字的匿名函式來說,local
是個閒置變數,然而該匿名函式的外圍函式(enclosing function)宣告了local
區域變數,因而粗體字匿名函式綁定了外圍函式的 local
區域變數。區域變數理應在函式呼叫過後即失去其作用。在上例中,網頁資源載入完成後會呼叫向
onload
事件註冊的函式,呼叫過後,local
變數理應失去作用,然而因為傳遞給 setInterval
的匿名函式中的閒置變數 local
綁定了外圍函式 local
變數,因此 local
變數的生命週期被延續了,在傳給 setInterval
的匿名函式存在期間,local
變數也會一直存活。這就好比
local
原本號稱要與 init
函式海枯石爛,現在卻跟著匿名函式跑了一樣…XD那實際如何應用 Closure 呢?常見的應用之一,就是在 JavaScript 中模擬私用性(private)。我們知道,JavaScript 本身是基於原型的(Prototype-based)語言,對於熟悉基於類別的(Class-based)語言使用者,經常需要模擬類別,而對於類別私有成員封裝,JavaScript 並沒有
private
之類的關鍵字,此時可以使用 Closure 加以模擬。例如:
function Account(bal) {
var balance = bal;
this.getBalance = function() {
return balance;
};
this.deposit = function(money) {
if(money > 0) {
balance += money;
}
};
}
var account = new Account(1000);
account.deposit(500); // OK
account.getBalance(); // OK
account.balance = 1000; // Error
上例是個用來模擬類別的典型範例,最後一行是錯誤的,因為 account
物件上並沒有 balance
特性(Property)。如果暫時不考慮一些細節的話,上例在 var account = new Account(1000);
時,相當於:
var account = {};
Account.call(accoount, 10000);
在我前一篇文章中談過,JavaScript 中,函式實際上是物件,因而也可以擁有方法。JavaScript 中每個函式物件都會擁有 call
方法,第一個參數接受函式中 this
實際的參考物件,第二個參數為函式物件上定義的第一個參數。也就是說,對照前一個模擬 Account
類別的 Account
函式,call
呼叫 Account
函式的過程中,在 this
,也就是 call
傳入的物件上新增了 getBalance
與 deposit
特性,分別參考至一個匿名函式,而這些匿名函式分別綁定了 balance
變數,也就是分別形成了 Closure。因此當你透過 account
物件的 getBalance
與 deposit
特性呼叫函式時,是可以存取 balance
變數的。然而,account 物件上並沒有新增
balance
特性,balance
變數是 Account
函式的區域變數,因此無法直接存取,這就達成了私有性模擬的目的。看來,
balance
變數可以橫跨多個 Closure,所以若要用比擬的方式來說,就像是腳踏多條船吧!…XD以上的討論,大概讓我們瞭解 Closure 的基本概念與作用,我不打算談太多 JavaScript 中閉包的應用,有興趣的話,可以參考 JavaScript Essence: 閉包(Closure)。
我們將逐步討論不同語言中對一級函式與閉包的支援,逐步帶出 Java 中引入 Lambda 語法的考量點有哪些。下一篇文章,會先來看看 Python 3 是如何支援一級函式與閉包。