什么是閉包
1、一個含有自由變量的函數(shù)辉巡;
2恨憎、這些自由變量所在的環(huán)境。
也就是函數(shù)和環(huán)境的總和構(gòu)成一個閉包郊楣。外部環(huán)境持有內(nèi)部函數(shù)所使用的自由變量憔恳,對內(nèi)部函數(shù)形成“閉包”。
簡單但不嚴(yán)格的說净蚤,一個函數(shù)的“自由變量”就是既不是參數(shù)也不是局部變量的變量(外部變量)钥组。
一個純粹(無副作用)的函數(shù)如果不含有自由變量,那么每次用相同的參數(shù)調(diào)用后的得到的結(jié)果肯定是一樣的今瀑。但如果一個函數(shù)含有自由變量程梦,那么調(diào)用返回的結(jié)果不但依賴于參數(shù)的值腔丧,還依賴于自由變量的值。因此一個含有自由變量的函數(shù)要正確執(zhí)行作烟,必須保證其所依賴的外圍環(huán)境的存在愉粤。
在JS中的樣子如下
function a() {
var i = 0;
function b() { alert(++i); }
return b;
}
var c = a();
c(); //1
c(); //2
閉包和面向?qū)ο?/h3>
閉包與對象是從兩個完全不同的角度描述了一件事情:一段代碼與其環(huán)境的關(guān)系。
所以可以用對象來模擬(實現(xiàn))閉包拿撩,也可以用閉包來模擬(實現(xiàn))對象衣厘。
剛剛閉包里的例子的效果完全可以用對象做出來, 這兩種方式都體現(xiàn)了函數(shù)和環(huán)境之間的關(guān)系压恒。
class a {
private int i = 0;
int b( ) { System.out.priintly(++i); }
}
a c = new a();
c.b //1
c.b //2
當(dāng)然嚴(yán)格來說方法所捕獲的自由變量不是i影暴,而是this;i是通過this來訪問到的探赫,完整寫出應(yīng)該是this.i型宙。
閉包的傳值
讓我們考察下面兩種情況:
只有值捕獲(capture-by-value):只需要在創(chuàng)建閉包的地方把捕獲的值拷貝一份到對象里即可。Java的匿名內(nèi)部類和Java 8新的lambda表達式都是這樣實現(xiàn)的伦吠。
有引用捕獲(capture-by-reference):把被捕獲的局部變量“提升”(hoist)到對象里妆兑。C#的匿名函數(shù)(匿名委托/lambda表達式)就是這樣實現(xiàn)的。參考Eric Lippert大神對“hoist”一詞的講解毛仪。不要把這個“hoist”的用法跟JavaScript里說的把局部變量提前到函數(shù)開頭來聲明的那種用法混為一談搁嗓。
如果變量(variable)是不可變(immutable)的,那么使用者無法感知值捕獲和引用捕獲的區(qū)別箱靴。
- 有些語言(例如C++11)允許顯式指定捕獲列表以及捕獲方式(值捕獲還是引用捕獲)腺逛,這樣最清晰,不過寫起來比較長衡怀;
- 有些語言(例如JavaScript)只有引用捕獲棍矛,要模擬值捕獲的效果需要手動新建閉包和局部變量;有些語言(例如C#)對不可變量(const local)做值捕獲抛杨,對普通局部變量做引用捕獲够委;由于無法感知對不可變量的值捕獲與引用捕獲的區(qū)別,統(tǒng)一把這個行為描述成是引用捕獲更方便一些蝶桶。
- 有些語言(例如Java)雖然目前只實現(xiàn)了值捕獲慨绳,但是還要留著面子不承認自己只做了值捕獲,所以只允許捕獲不變量(final local)真竖,或者例如Java 8允許捕獲事實上不變量(effectively final local)。這樣厌小,雖然實現(xiàn)用的是值捕獲恢共,但效果看起來跟引用捕獲一樣;就算以后的Java擴展到允許通用的(對可變變量的)引用捕獲璧亚,也不會跟已有的代碼發(fā)生不兼容讨韭。
public class Test {
public static void main(String[] args) {
}
public void test(final int b) {
final int a = 10;
new Thread(){
public void run() {
System.out.println(a);
System.out.println(b);
};
}.start();
}
}
- 有些語言(例如Python)的lambda略奇葩,實現(xiàn)的是引用捕獲,但是lambda內(nèi)不能對捕獲的變量賦值透硝,只有原本定義那些變量的作用域里能對它們賦值狰闪。