閉包(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指向系列文章目錄
友情鏈接:點擊查看所有文章目錄