公司新來的女實習生問我什麼是閉包?

觀感度:🌟🌟🌟🌟🌟

口味:冰鎮西瓜

烹飪時間:20min

撩妹守則第一條,女孩子都喜歡童話故事。

那就先來講一個童話故事~

// 有一個公主
// 她生活在一個充滿冒險的奇妙世界裏
// 她遇見了她的白馬王子,帶着她騎着獨角獸環遊世界
// 與龍搏鬥,遇到了會說話的松鼠,以及許多其他幻想的事情。
function princess () {
    var adventrures = [];
    function princeCharming () {};
    var unicorn = {};
    var dragons = [];
    var squirrel = "Hello!";
    // 但她不得不回到她那充滿家務和大人們的單調世界。
    return {
    // 她經常給身邊的人講她作爲一個公主的奇妙經歷。
      story:function () {
          return adventures[adventures.length - 1];
      }
    }
}
// 但他們看到的只是一個小女孩在講述關於魔法和幻想的故事
var littleGril = princess();
littleGril.story();
// 即使大人們知道她是真正的公主,他們也不會相信所謂的獨角獸或龍,因爲他們永遠看不到它們
// 大人們說它們只存在於小女孩的想象中
// 但我們知道真正的真理
// 裏面的小女孩真的是個公主

這個故事來自於stackoverflow的一則回答,看不懂沒關係,等閱讀完本文後,回頭再來看這個故事,你會發現你已經完全瞭解了我的魅力,咳咳@¥%#…………JavaScript中閉包的魅力。

30.jpg

什麼是閉包?

當函數可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函數是在當前詞法作用域之外執行。 --- 你不知道的JavaScript(上卷)

來個🌰

function demo() {
    var a = 1;
    return function () {
        return a; 
    }
}

var a = demo();
console.log(a());  // 1

閉包的構成

閉包由兩部分構成:函數,以及創建該函數的環境。

環境由閉包創建時在作用域中的任何局部變量組成。

閉包的本質

閉包其實是JavaScript函數作用域的副作用產品。

閉包是一種特殊的對象。

所謂有意栽花花不開,無心插柳柳成蔭,不是JavaScript故意要使用閉包,而是由於JavaScript的函數內部可以使用函數外部的變量,這段代碼又剛剛好符合閉包的定義。

31.jpg

JavaScript中,外部函數調用之後其變量對象本應該被銷燬,但閉包阻止了它們的銷燬,我們仍然可以訪問外部函數的變量對象。

進一步的說,通常情況下,函數的作用域及其所有變量都會在函數執行結束後被銷燬。但是,如果創建了一個閉包的話,這個函數的作用域就會一直保存到閉包不存在爲止。

function addCalculator (x) {
    return function (y) {
        return x + y;
    }
}

var add1 = addCalculator(1);

console.log(add1(1)); //2

// 釋放對閉包的引用
add1 = null;

console.log(add1(1)); //Uncaught TypeError: add1 is not a function

閉包的應用

我們可以用閉包來做什麼呢?

瞭解Java的同學可能知道,Java是支持私有方法的,私有方法只能被一個類中的其他方法所調用,但是JavaScript沒有提供這種原生支持,所以我們可以通過閉包來模擬私有方法。

私有方法自然有私有方法的好處,私有方法有利於限制對代碼的訪問,而且可以避免非核心的方法干擾代碼的公共接口,減少全局污染。

來個🌰

var calculator = (function(){
    var a = 1;
    function addCalculator(val){
        a += val
    }
    return {
        add1:function() {
            addCalculator(1);
        },
        add2:function() {
            addCalculator(2);
        },
        result:function() {
            return a
        }
    }
})();

console.log(calculator.result());  // 1
calculator.add1();
console.log(calculator.result());  // 2
calculator.add2();
console.log(calculator.result());  // 4

上面這種方式也叫做模塊模式(module pattern)

使用閉包的注意事項

內存泄漏

因爲閉包可以使函數中的變量都保存在內存中,造成很大的內存消耗,所以如果 不是某些特定的任務需要使用閉包,我們不要濫用它。

很多博客中都提到了這一點,但是其實都是不完全對的。

敲黑板!!!

使用不當的閉包會在IE(IE9)之前造成內存泄漏問題。因爲它的JavaScript引擎使用的垃圾回收算法是引用計數法,對於循環引用將會導致GC(下文會介紹)無法回收垃圾。

關於各個瀏覽器的閉包測試,詳情請見司徒正美-js閉包測試

垃圾回收機制

都9102年了,全國開始實行垃圾分類了,你居然還不知道垃圾回收機制,趕快來補習一下!

32.jpg

垃圾回收也就是GC(Garbage Collection)

GC把程序不用的內存空間視爲垃圾,找到它們並且將它們回收,讓程序員可以再次利用這部分空間。

不是所有的語言都有GC,一般存在於高級語言中,如JavaJavaScriptPython。那麼在沒有GC的世界裏,程序員就比較辛苦,只能手動去管理內存,比如在C語言中我們可以通過malloc/free,在C++中的new/delete來進行管理。

垃圾回收算法

因爲這一部分的內容很多,本文只進行簡單的講解,如果想深入瞭解垃圾回收算法的同學可以在文末獲取學習資料。

GC標記-清除算法

世界上首個值得紀念的GC算法是GC標記-清除算法。因爲自其問世以來,一直到半個世紀後的今天,它依然是各種處理程序所用的偉大的算法。

GC標記-清除算法由標記階段和清除階段構成,標記階段將所有的活動對象做上相應的標記,清除階段把那些沒有標記的對象,也就是非活動對象進行回收。在搜索對象並進行標記的時候使用了深度優先搜索,儘可能的從深度上搜索樹形結構。

優點:

1.算法簡單,實現容易。

2.與保守式的GC算法兼容。

缺點:

1.在使用過程中會出現碎片化的情況,如同Windows的文件系統一樣,導致
無數的小分塊散佈在堆的各個地方。

2.分配速度,由於分塊的不連續性,算法每次分配的時候都需要遍歷空閒鏈表爲了找到足夠大的分塊,這樣最糟糕的情況就是遍歷到最後才找到合適的分
塊,影響了分配速度。

引用計數法

這種方法中引入了計數器的概念,通過計數器來表示對象的“人氣指數”,也就是有多少個程序引用了這個對象。當計數器(引用數)爲0時,垃圾立刻被回收。

優點:

1.可以立即回收垃圾。

2.最大暫停的時間短。

3.並且沒有必要沿指針查找。

缺點:

1.上文提到過的循環引用無法回收。

2.並且實現起來很複雜。

3.計數器值的增減處理十分繁重。

4.同時計數器需要佔很多位,導致內存空間的使用效率大大降低。

軟件工程沒有銀彈,這些缺點也都有相應的辦法進行解決,如果你想深入瞭解垃圾回收算法,可以購買垃圾回收的算法與實現這本書去看,建議支持正版。

歡迎關注我的個人公衆號,文章將同步發送,後臺回覆【福利】即可免費領取海量學習資料。

你的前端食堂,記得按時吃飯。

ps:你也可以後臺回覆垃圾回收即可領取相關資料一份~

gongzhonghao.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章