閉包和類

閉包

先上維基百科的定義javascript

在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即便已經離開了創造它的環境也不例外。因此,有另外一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運行時能夠有多個實例,不一樣的引用環境和相同的函數組合能夠產生不一樣的實例。html

簡單理解這句話,有兩個要點:
1. 自由變量 2. (引用自由變量的)函數。java

先說自由變量:

當咱們定義一個變量時,若是不對它指定約束條件,它就是自由變量。 舉個例子:程序員

x ∈  (0,99)
f(x,y)

在函數f(x,y)中,x就是約束變量,y是自由變量。編程

具體到JavaScript中,看一個例子:數組

var x = 0;
function foo (y) {
    var z = 2;
    return x + y + z;
}
foo (3); // 3

轉換成數學思惟的話,函數foo其實應該是這樣的foo(x,y),可是咱們知道函數的參數實際上是受到函數的約束的,也就是說,真正的自由變量只有x一個。
這樣能夠引出一個簡單的定義,在函數中,若是存在一個既不是局部變量,也不是形參的變量,咱們能夠認爲造成了閉包。閉包

自由變量從哪兒來?

幾乎全部的語言中,對於同名變量都是就近尋找,先在本做用域內尋找,找不到就去父做用域找。咱們稱之爲做用域鏈。
在一個閉包函數中,自由變量一般是由父級提供。看下面的例子:app

function foo(x) {
    var tmp = 3;
    function bar(y) {
        console.log(x + y + (++tmp));
    }
    bar(10);
}
foo(2)

根據咱們上面的定義,bar擁有自由變量,是閉包,而foo不是。
那麼怎樣才能讓foo變成閉包呢?編程語言

var x = 0;
function foo() {
    var tmp = 3;
    function bar(y) {
        console.log(x + y + (++tmp));
    }
    bar(10);
}
// 其實轉換一下,形如
function foo2() {
    var tmp = 3;
    //function bar(y) {
        console.log(x + 10 + (++tmp));
    //}
    // bar(10);
}

此時,能夠認爲foo是一個閉包。
到這裏,可能有朋友以爲這和平時看到的js閉包不同啊,咱們平時看到的閉包,都是這樣的:例子來自這篇博客函數

function foo(x) {
    var tmp = new Number(3);
    return function (y) {
        alert(x + y + (++tmp));
    }
}
var bar = foo(2); // bar 如今是一個閉包
bar(10);

這個函數其實能夠改寫成下面的樣子:

bar = function (y) {
    // foo(2)
    alert(2 + y + (++tmp))
}

很明顯,tmp是自由變量,符合咱們起初的定義,bar是擁有自由變量的函數。
那麼tmp存在哪兒呢?
在執行foo(2)時,就會產生一個tmp=3的變量。這個變量被return的函數所引用,因此不會被回收。而return的函數中的自由變量,根據做用域鏈去尋找值。bar函數,是在foo(2)中定義的,因此,變量tmp先在foo(2)的變量區中去尋找,並對其操做。

注:有關做用域鏈的問題,我會在下一篇作解析。

說到這裏,插一下module模式

閉包使用之module模式

var Module = (function () {
    var aaa = 0;
    var foo = function () {
        console.log(aaa);
    }
    
    return {
        Foo: foo
    }
})();
// 或者
(function () {
    var aaa = 0;
    var foo = function () {
        console.log(aaa);
    }
    
    window.Module = {
        Foo: foo
    }
})();

注意上面的兩個例子,Module自己只是一個對象,可是return的函數自己造成了閉包,保證了做用域的乾淨,不會污染到其餘函數。

說到這裏,想必有朋友以爲這不就是個另類的類嗎?擁有局部變量,還有可訪問的函數。沒錯,就外現而言,我認爲閉包和類是很是類似的。

以Java舉例:

class Foo {
    private int a;
    int Say( int b ) {
        return a + b; 
    }  
}

上面的Foo中,函數Say中的a是函數做用域外的,屬於自由變量。能夠認爲Say造成了函數閉包。可是與js不一樣的地方就在於,實例方法須要經過類的實例也就是對象來調用。
在java的設計裏,明確了訪問權限,private,protect,default,package,這是規範調用的創舉。這也使得java程序員不多會考慮閉包這種實現,由於變量和函數都有關鍵字來定義訪問權限,歸屬於一個個類中,明確且清晰。

閉包的壞處

若是把閉包按照類的實現來理解的話,很容易就明白爲何不建議使用閉包。
每次調用閉包,就會生成一個做用域來存放一些閉包函數須要的自由變量。這很容易形成內存浪費。即便在Java編程中,也不建議隨便就新建對象。

題外話

在前一篇bind、call、apply中,我提到了一個觀點,由於是面向對象,因此存在綁定this的須要。
關於面向對象,我認爲,面向對象的好處就在於,易於理解,方便維護和複用。這在多人開發大型項目時,是遠遠超過對性能的要求的。
即便在摩爾定律放緩的如今,相對於之前,內存也是很是便宜的,因此從遠古時代對於性能要求到極致,到如今廣泛提倡代碼可讀性。
有超級大牛建立了這個繽紛的代碼世界,爲了讓更多人體會到編程的樂趣,他們設計了更易理解的編程語言,發明了各類編譯器、解析器……
若是隻是寫一個1+1的程序,是不須要面向對象的,若是人類能和機器擁有超強的邏輯和記憶,也是不須要面向對象的。

相關文章
相關標籤/搜索