1、一個(gè)含有自由變量的函數;
2、這些自由變量所在的環(huán)境。
外部環(huán)境持有內部函數所使用的自由變量,對內部函數形成“閉包”,就這樣。
簡(jiǎn)單但不嚴格的說(shuō),一個(gè)函數的“自由變量”就是既不是參數也不是局部變量的變量。
一個(gè)純粹(無(wú)副作用)的函數如果不含有自由變量,那么每次用相同的參數調用后的得到的結果肯定是一樣的。但如果一個(gè)函數含有自由變量,那么調用返回的結果不但依賴(lài)于參數的值,還依賴(lài)于自由變量的值。因此一個(gè)含有自由變量的函數要正確執行,必須保證其所依賴(lài)的外圍環(huán)境的存在。
基于類(lèi)的面向對象程序語(yǔ)言中有一種情況,就是方法是用的自由變量是來(lái)自其所在的類(lèi)的實(shí)例的。像這樣:
Java代碼
class Foo {
private int x;
int AddWith( int y ) { return x + y; }
}
這樣的AddWith()有一個(gè)參數y和一個(gè)自由變量x,其返回的值既依賴(lài)于參數的值也依賴(lài)于自由變量的值。為了讓AddWith()正確工作,它必須依附于Foo的一個(gè)實(shí)例,不然就得不到x的值了(稱(chēng)為:“變量i未與值相綁定”)。很好理解對吧。不過(guò)面向對象的語(yǔ)言里一般不把類(lèi)稱(chēng)為閉包,沒(méi)為什么,就是種習慣。
當然嚴格來(lái)說(shuō)方法所捕獲的自由變量不是i,而是this;x是通過(guò)this來(lái)訪(fǎng)問(wèn)到的,完整寫(xiě)出應該是this.x。
如果這個(gè)“外圍環(huán)境”來(lái)自一個(gè)外圍函數,并且內部函數可以作為返回值返回,那么外圍函數的局部環(huán)境就不能在調用結束時(shí)就撤銷(xiāo)。也就是說(shuō)不能在棧上分配空間。
Js代碼
function AddWith(x) {
return function(y) {
return x + y
}
}
這樣的內部函數有一個(gè)參數y和一個(gè)自由變量x。x在外圍函數AddWith()里是一個(gè)參數,也就是一個(gè)“已綁定了值的變量”(bound variable)。AddWith()的局部作用域中含有內部函數所使用的自由變量,對內部函數形成閉包。為了讓返回出去的內部函數能正常工作,這個(gè)內部函數必須依附于一個(gè)能提供x的值的環(huán)境,也就是AddWith()提供的閉包。這樣我們就能夠:
Js代碼
var addFive = AddWith(5)
var seven = addFive(2) // 2+5=7
全局變量是一種特殊的自由變量。
收先先了解一下什么是閉包,
閉包 是可以包含自由(未綁定)變量 的代碼塊;這些變量不是在這個(gè)代碼塊或者任何全局上下文中定義的,而是在定義代碼塊的環(huán)境中定義。“閉包” 一詞來(lái)源于以下兩者的結合:要執行的代碼塊(由于自由變量的存在,相關(guān)變量引用沒(méi)有釋放)和為自由變量提供綁定的計算環(huán)境(作用域)。
可能上面的定義有點(diǎn)晦澀,下面看一下《Python 核心編程》 對 閉包 的解釋。
如果在一個(gè)內部函數里,對在外部作用域(但不是在全局作用域)的變量進(jìn)行引用,那么內部函數就被定義為閉包 。定義在外部函數內的但由內部函數引用或者使用的變量被稱(chēng)為自由變量 。
下面是一個(gè)閉包的例子 (由于JAVA現在不支持閉包,這個(gè)閉包的例子是用Python寫(xiě)的,參見(jiàn)了《Python 核心編程》 )
Python代碼
def counter(start_at = 0):
count = [start_at]
def incr():
count[0] += 1
return count[0]
return incr
這里面count變量 就是一個(gè) 相對于函數incr 的自由變量(它在 函數incr 的外部作用域上,但又不在全局作用域上),內部函數incr 可以引用和使用這個(gè)變量。這個(gè)例子主要模擬一個(gè)計數器。
運行下面的代碼
Java代碼
count = counter(6)
print count()
print count()
就會(huì )打印出
7
8
我們發(fā)現 內部函數(incr)不但可以引用其自身定義的變量,還可以引用外部函數(counter)定義的變量。
或者說(shuō) 內部函數(閉包) 可以記憶狀態(tài), 它可以根據 它記憶的狀態(tài) 來(lái)執行不同的操作。 而外部函數 負責
初始化狀態(tài)(內部函數需要記憶的狀態(tài))。
那么為什么需要閉包,閉包的優(yōu)勢是什么呢?
我覺(jué)得就是可以記憶狀態(tài)
但對象也可以記憶狀態(tài)(通過(guò)對象的屬性)。
那閉包和對象的區別是什么呢?
我覺(jué)得 就因為 閉包是函數 而不是對象。
我們會(huì )發(fā)現,如果用面向對象的方式來(lái)表達閉包
內部函數(閉包)就像 對象的方法
而外部函數 對象的構造器。
構造器 用來(lái) 初始化對象狀態(tài)
而 對象的方法可以根據 對象的狀態(tài) 來(lái)執行不同的操作。
好! 下面我們用面向對象的方式 創(chuàng )建一個(gè) 計數器(實(shí)現和上例一樣的功能,用JAVA實(shí)現)。
Java代碼
public class Counter {
private int startAt;
public Counter() {
this(0);
}
public Counter(int startAt) {
this.startAt = startAt;
}
public int incr(){
return ++ this.startAt;
}
}
運行Test類(lèi)
Java代碼
public class Test{
public static void main(String[] args){
Counter counter = new Counter(6);
System.out.println(counter.incr());
System.out.println(counter.incr());
}
}
會(huì )打印出(和上例打印輸出相同)
7
8
那么JAVA(有了對象)還需要引入閉包嗎?
我覺(jué)得不需要,
因為對象完全可以模擬 閉包 的行為,
而且 對象 才是 OOP 的 一級元素。
閉包 是 函數式編程(FP) 中的 概念,
引入閉包 就相當于 引入 FP,
這只會(huì )破壞 JAVA 的 純粹 與 簡(jiǎn)單 。。
javascript面向對象技術(shù)基礎(六)