認識 Lambda/Closure(2)什麼是 Closure?


English

簡化來說,Closure 是擁有閒置變數(Free variable)的運算式。閒置變數真正扮演的角色依當時參考的語彙環境(Lexical environment)而定。支援閉包的程式語言通常具有一級函式(First-class function)。建立函式不等於建立閉包。如果函式的閒置變數與當時語彙環境綁定,該函式才稱為閉包。
那麼何為閒置變數?閒置變數是指對於函式而言,既非區域變數也非參數的變數,像區域變數或參數,其作用範圍基本上在被定義的函式範圍中。它是被綁定變數(Bound variable)。
有沒有白話一點的寫法?唔!…就是… understanding-lambda-closure-2-1 舉個例子來說:
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 傳入的物件上新增了 getBalancedeposit 特性,分別參考至一個匿名函式,而這些匿名函式分別綁定了 balance 變數,也就是分別形成了 Closure。因此當你透過 account 物件的 getBalancedeposit 特性呼叫函式時,是可以存取 balance 變數的。
然而,account 物件上並沒有新增 balance 特性,balance 變數是 Account 函式的區域變數,因此無法直接存取,這就達成了私有性模擬的目的。
看來,balance 變數可以橫跨多個 Closure,所以若要用比擬的方式來說,就像是腳踏多條船吧!…XD
以上的討論,大概讓我們瞭解 Closure 的基本概念與作用,我不打算談太多 JavaScript 中閉包的應用,有興趣的話,可以參考 JavaScript Essence: 閉包(Closure)
我們將逐步討論不同語言中對一級函式與閉包的支援,逐步帶出 Java 中引入 Lambda 語法的考量點有哪些。下一篇文章,會先來看看 Python 3 是如何支援一級函式與閉包。