閉包簡介

1. 何爲閉包

在學習函數式語言、動態語言時會經常接觸到一個概念——閉包,例如python、scala中都有對閉包的支持。

翻看網上各種資料,對於閉包的解釋都比較晦澀、學術。

比如百科中這段解釋:“在計算機科學中,閉包Closure)是詞法閉包Lexical Closure)的簡稱,

是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。

所以,有另一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。

閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。”

總結這段話,閉包的定義有兩種:

(1)引用了自由變量的函數(自由變量是指除局部變量以外的變量);

(2)函數和與其相關的引用環境組合而成的實體。

這兩種定義是不同,(1)認爲閉包就是函數,與普通函數的區別是引用了自由變量,(2)認爲閉包是一個實體,不同於函數

在學習python時對閉包的理解是一個函數綁定了定義它時的上下文,(2)更符合閉包實際應用時直覺。

2. 創建閉包

下面從scala、python、javascript三種語言來說明如何創建一個閉包。

只簡單介紹如何創建閉包,並不對三種語言閉包實現差異作比較。

2.1. scala閉包

def a() = {
  val x = 1
  def b() = {
    print(x)
  }
  b
}
val f = a()
f

通過如上返回一個函數的方式會創建一個閉包,可以發現變量x是局部變量,

但在外部通過函數f也可以訪問函數a的私有變量,就像擁有了類的特性。

2.2. python閉包

def make_adder(addend):
    def adder(augend):
        return augend + addend
    return adder
 
p = make_adder(23)
q = make_adder(44)
 
print p(100)
print q(100)

從上面的代碼就可以更明顯的看出閉包讓函數具有了類的特性,其實閉包是具有數據的行爲,而對象是具有行爲的數據。

2.3. javascript閉包

function f1(){
    var n = 1;
    function f2(){
        alert(n); 
    }
    return f2;
}
var f=f1();
f();

從上面可以看出三種語言閉包功能的實現大同小異,本質上提供一種函數外部訪問局部變量的能力。

3. 閉包的應用場景

其實在學習一個技術時,最主要是它的應用場景,因爲只學不用的話會很快忘記這個技術。 

3.1.  細粒度的模塊化

對列表的遍歷操作,使用頻率非常高,比如打印列表中每個對象、列表求和、列表乘積。

在C語言中實現這樣的功能需要每次都利用for來遍歷數組,然後寫出具體的操作邏輯。

缺點是明顯的,不斷重複遍歷語句,代碼文件變得冗長。

在面嚮對象語言中,比如java,實現這樣的功能也是需要重複遍歷列表的。當然也可以爲列表封裝一個類,

裏面定義了針對數據的操作邏輯,sum、multiply、print等。當每次new一個類時,每個類都有自己的成員方法。

這樣做的缺點是:內存佔用較多,因爲每個對象維護了自己的成員方法;擴展性較差,如果有其他的類似操作,比如爲列表中每個值

加上固定值,那麼就要修改類的定義了。

其實對上述操作抽象出來公共的行爲是遍歷操作,那麼只需要建立一個遍歷行爲功能,所有其他遍歷時的具體操作,由調用時來指定即可。

那麼函數式語言中的實現如下

scala> val a = List(1, 2, 3, 4, 5)
a: List[Int] = List(1, 2, 3, 4, 5)
scala> var sum = 0
sum: Int = 0
scala> a.foreach(x => sum = sum + x)
scala> sum
res1: Int = 15

上述的foreach遍歷操作是一個公共的操作,在調用它時,傳遞了一個求和函數。

這可能與回調函數非常相似,但是請注意,在這個例子中,回調函數引用了定義它的當前環境的變量sum,並且這種對sum的改變直接反應在

當前的上下文中,不需要return操作。這種功能是回調函數不具備的。

擴展性也較好,可以實現功能完全取決於在調用foreach時傳遞的函數功能。

3.2. 利用閉包模擬面向對象編程

在scala和python這種原生就支持面向對象編程的語言中來解釋這個例子可能不太合適,

解釋這個特性需要在不支持面向對象技術但支持閉包的語言中舉例,例如javascript。

下面的實例利用閉包特性模擬一個類

function myObject(n){
    function f1(){
        alert(n);
    }
    function f2(){
        n = n +1
        alert(n);
    }
    var o = {};
    o.f1 = f1;
    o.f2 = f2;
    return o;
}

使用這個類

var o1 = myObject(5);
o1.f1();
o1.f2();
var o2 = myObject(9);
o2.f1();
o2.f2();

當然閉包的應用場景遠不止上述羅列出兩點,由於目前沒有接觸過更多的閉包的使用實例,需要繼續發現和探索。

4. 總結

可以看出閉包的特性是非常重要的,能幫助我們更優雅的實現某些功能,簡化代碼。當然閉包對於內存的消耗也是非常大的,需要保存函數的上下文,

目前在硬件的容量、性能越來越好的情況下,當然鼓勵去更多使用閉包。

發佈了106 篇原創文章 · 獲贊 103 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章