深入理解javascript系列(九):應用閉包

Panthon發表於2018-06-13

理論是自信的基礎,結合理論的實踐才能讓我們走的更遠。

前兩個系列,我記錄了閉包的學習,如何利用閉包解決實際問題了?其實,很多東西你我都知道,不是一蹴而就的,不是你今天學了就會了,還需要多次練習,反覆練習。相信終究一天你我會運用自如。

下面就通過3個小例子,來運用閉包解決實際問題吧。

9.1  迴圈、setTimeout與閉包

在面試題中常常會遇到一個與迴圈、閉包有關的問題,如下:

//利用閉包的知識修改這段程式碼,讓程式碼的執行結果為隔秒輸出1,2,3,4,5
for (var i = 1; i<=5; i++) {
    setTimeout(timer=()=>{
        console.log(i)
    },i*1000)
}複製程式碼

首先來分析一下如果直接執行這個例子會輸出什麼結果。(涉及到定時器執行緒的相關知識還需要自行學習)

前面我們已經知道,for迴圈的大括號並不會形成自己的作用域,因此這個時候肯定是沒有閉包產生的,而i值作為全域性的一個變數,會隨著迴圈的過程遞增。因此迴圈結束之後,i變成了6。

而每一個迴圈中,setTimerout的第二個引數訪問的都是當前的i值,因此第二個i值分別是1,2,3,4,5。而第一個引數timer函式中雖然訪問的是同一個i值,但是由於延遲的原因,當timer函式被setTimerout執行時,迴圈已經結束,即i已經變為6了。

因此這段程式碼執行結果是隔秒輸出6.

而我們想要的是隔秒輸出1,2,3,4,5,因此需要藉助閉包的特性,將每一個i值都用一個閉包保護起來。每一輪迴圈,都把當前的i值儲存在一個閉包中,當setTimerout中定義的操作執行時,訪問對應閉包即可。

這個時候我們回想一下閉包形成的條件,簡單來說,就是一個函式中定義了一個子函式,子函式內部訪問了函式的變數物件。因此我們只需要建立一個這樣的環境即可。

for (var i = 1; i<=5; i++) {
    (function(i) {
        setTimeout(timer=()=>{        console.log(i)
    },i*1000)    })(i)
}複製程式碼

定義一個匿名函式,稱作A,並將其當作閉包環境。而timer函式則作為A的內部函式,當A執行時,只需訪問A的變數物件即可。因此將i值作為引數傳入,這樣也就滿足了閉包的條件,並將i值儲存在了A中。

同樣的道理也可以在timer函式裡做文章。還有更多方法,這裡就不一一贅述。

9.2  單例模式與閉包

好,我繼續我的筆記。

在javascript中有許多解決特定問題的編碼思維(設計模式,Alloy Team出的那本個人覺得很棒),例如工廠模式、釋出訂閱模式、裝飾者模式、單例模式等。其中,單例模式是早期開發最常用的模式之一,而它的實現,與閉包慼慼相關。

所謂單例模式,就是隻有一個例項。

1.  最簡單的單例模式

物件字面量的方法就是最簡單的單例模式,我們可以將屬性與方法依次放在字面量裡。

var person = {
    name: 'pan',
    age: '18',
    getName: function() {
        return this.name
    },
    getAge: function() {
        return this.age
    }
}複製程式碼

但是這樣的單例模式有一個嚴重的問題,即它的屬性可以被外不修改。因此在許多場景中,這樣的寫法並不符合我們的需求,我們更期望物件能夠有自己的私有方法與屬性。

PS:個人覺得不論學習什麼,都應該回顧它的歷史,以便更利於當下的學習。理論性知識發展到目前,不是偶然,總有那麼一個階段性的過渡。司徒正美在他的作品《javascript框架設計》前言中就說到了這麼一個事“當初,我閱讀jQuery原始碼,最初看的是1.4.3版本,看得一頭霧水,一氣之下,從最初的1.0版本開始看。看完所有版本,瞭解其迭代過程,才明白”。

2.  有私有方法/屬性的單例模式

通過前面所學的知識我們很容易就能想到,想要一個物件擁有自己私有的方法屬性,那麼只需要建立一個單獨的作用域將物件與外界隔離起來就行了。這裡我們藉助匿名函式自執行的方式即可。

var person = (function() {
     var name = 'pan';
     var age = 18;
 
     return{
        function getName() {        return name
     };
     function getAge() {
         return age
     }    }   
})();
person.getName();複製程式碼

私有變數的好處在於,外界對於私有變數能夠進行什麼樣的操作是可以控制的。我們可以提供一個getName方法讓外界可以訪問名字,也可以額外提供一個setName方法,來修改它的名字。對外提供什麼樣的能力,完全由我們自己決定。

現在我們正走在模組化的道路上....

3.  呼叫時才初始化的單例模式

有的時候(使用頻次較少)我們希望自己的例項僅僅只是在呼叫的時候才被初始化,而不像上面兩個例子那樣,即使沒有呼叫person,person的例項在函式自執行的時候就返回了。

那麼我們就需要在上面例子的基礎上做一點小小的改動了。

var person = (function(){
    //定義一個變數,用來儲存例項
    var instance = null;
    var name = 'pan';
    var age = 18;

    //初始化方法
    function init() {
        return{
            getName: function() { return name;},
            getAge: function() { return age;}
        }
    }

    return {
        getInstance: function() {
                if(!instance){
                    instance = init()
                }
                return instance
            }
    }
})();

//只在使用時獲取例項
var p1 = person.getInstance();複製程式碼

在這個例子中,我們對匿名函式中定義了一個instance變數用來儲存例項。在getInstance方法中判斷了是否對他進行重新賦值。由於這個判斷的存在,因此變數instance僅僅只在第一次呼叫getInstance方法時賦值了。

在寫一個系列,我將持續更新我閉包相關應用。感謝陽波大神。

這些都是我以往的學習筆記。如果您看到此筆記,希望您能指出我的錯誤。有這麼一個群,裡面的小夥伴互相監督,堅持每天輸出自己的學習心得,不輸出就出局。希望您能加入,我們一起終身學習。歡迎新增我的個人微訊號:Pan1005919589


相關文章