JavaScript之閉包

閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應用都要依靠閉包實現。
要理解閉包,首先必須理解JS作用域[點擊查看]

閉包的概念

在百度百科上是這麼解釋閉包的:
在這裏插入圖片描述
其實這個說法是很籠統的。準確來說,閉包是基於正常的垃圾回收處理機制下的。也就是說,一般情況一個函數(函數作用域)執行完畢,裏面聲明的變量會全部釋放,被JS垃圾回收機制回收。但閉包利用一個技巧,讓作用域裏面的變量,在函數執行完之後依舊保存沒有被垃圾回收機制處理掉。
下面我們先寫一個常用的閉包例子:

function person() {
    var num = 1
    return function () {
        return console.log(`劉家軍週日在敲代碼${num++}`);
    }
}
var doSth = person(); // doSth 現在是一個閉包
doSth();
doSth();

輸出:
在這裏插入圖片描述
我們來分析一下這一段代碼,首先執行var doSth = person();那麼person就執行了,但是執行完畢之後, 變量num並沒有被回收釋放,因爲返回值裏面還等待使用num,所以此時,person雖然執行了,但是person的變量並沒有被釋放,在return在等待繼續使用這些變量了,這個時候doSth就是一個閉包。

你每執行一次doSth,結果就變化一次,這就是閉包的神奇之處,它改變了JS的內存機制。

然後我們再看看長得很像閉包的形式

function person() {
      var num = 1
    function doSth() {
        console.log(`劉家軍週日在敲代碼${num++}`);
    }
    doSth()
}
person();
person();

輸出:
在這裏插入圖片描述
如果按照百度上所說的閉包可以讀取其他函數內部的變量,person在全局中執行,執行過程中也訪問到了num,但他不是閉包,我們來一遍它的執行過程,
函數person執行,執行完之後,再執行的時候,裏面的num,doSth函數又重新聲明瞭。那麼根本就沒有阻止person作用域中的變量被js垃圾回收機制所回收,所以就不能叫閉包,所以閉包要是一個定義在函數內部的函數

閉包的特性

我們從上面的例子可以總結出閉包有3個特性:

1.函數嵌套函數
2.函數內部可以引用函數外部的參數和變量
3.參數和變量不會被垃圾回收機制回收
再來看一個經典例子-定時器與閉包:
   for(var i = 0; i < 5; i++){
        setTimeout(() =>{
            console.log(i)
        },100)
    }

輸出:
在這裏插入圖片描述
結果輸出5次5,因爲js是單線程的,所以在執行for循環的時候定時器setTimeout被安排到任務隊列中排隊等待執行,而在等待過程中for循環就已經在執行,等到setTimeout可以執行的時候,for循環已經結束,i的值也已經編程5,所以打印出來五個5,如果對這一點還不是很清楚的可以看一下我前面所寫的關於JS異步系列的文章
那麼我們爲了實現輸出0,1,2,3,4修改下代碼,由於在ES5中沒有塊級作用域的說法,所以得利用函數自己創建一個作用域(如果把for循環裏面的var變成let,也能實現預期結果)

for(var i = 0; i < 5; i++){
     (function(j){    // 傳入參數j j = i
        setTimeout(() =>{
          console.log(j) 
     },100)
     })(i)
}

輸出:
在這裏插入圖片描述
這哪又用到了閉包我們分析下,for循環每一次都執行一個 匿名自執行函數,每一次變量 i 被當做參數傳到匿名自執行函數中去 , 那麼這個匿名自執行函數中創建了一個變量,延時定時器裏面需要用到這個參數 j ,但是因爲js異步操作,被添加到任務隊列中, 那麼這個遍歷 j 就沒有被清理 , 就一直被保存着 , 每一個匿名自執行函數都做一樣的事情 , 所以這個時候就產生了閉包 , 變量 j 並沒有被回收,依然在等待你使用。

閉包的優缺點

從上面所說的我們可以總結下閉包的優缺點

優點

1.保護函數內的變量安全 ,實現封裝,防止變量流入其他環境發生命名衝突
2.在內存中維持一個變量,可以做緩存
3.匿名自執行函數可以減少內存消耗

缺點

1.其中一點上面已經有體現了,就是被引用的私有變量不能被銷燬,增大了內存消耗,造成內存泄漏,解決方法是可以在使用完變量後手動爲它賦值爲null;
2.其次由於閉包涉及跨域訪問,所以會導致性能損失,我們可以通過把跨作用域變量存儲在局部變量中,然後直接訪問局部變量,來減輕對執行速度的影響

閉包與this的指向

直接上代碼:

var name = "window"  // 這是全局的
var obj = {
    name: "obj",  // 這是局部的
    func: function(){
    // 這裏是對象內的方法,這裏this指向obj
     return function(){
     // 這裏是閉包,this指向window,所以this.name就是window.name
     return  console.log(this.name)
   }
   }
}		
var test = obj.func()
test()

輸出:
在這裏插入圖片描述
閉包中的this指向的是window對象,this.name=window. name;
如果要改變閉包中的this有兩種方法;
1.因爲對象內的方法裏的this指向這個對象,所以可以再func裏定一個變量self代替this,閉包裏this. name改爲self. name
2.使用call方法,test.call(obj),不瞭解call的應用的可以看我之前的文章

關於閉包我就講到這了,明天我們再詳細講解下js中的this指向

友情鏈接:點擊查看 JavaScript作用域、閉包、this指向系列文章目錄

友情鏈接:點擊查看所有文章目錄

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