寫在前邊: 咱們知道,當函數執行時,會造成本身的執行期上下文,並把它掛到本身的做用域鏈上,當函數執行完以後,它的執行期上下文就會被釋放。因此,通常狀況下,在函數外部訪問函數內部的變量須要特殊的方法才能解決,這個特殊的方法就是閉包。es6
在理解閉包前,我建議你先了解下js的做用域。 理解js中的做用域面試
閉包:閉包指的是在函數的外部能夠訪問函數內部的變量。函數沒有被釋放,整條做用域鏈上的局部變量都將獲得保留。建立閉包的通常方法是在函數內部返回一個新函數。bash
通俗的理解: 閉包:顧名思義,是一個封閉的包,可是這個包露出內部的一條線,這條線就是閉包內部返回函數的做用域鏈,它上面掛載了這個函數以及他的全部父級函數的變量,咱們能夠經過這條線訪問到函數內的變量,這就是閉包。閉包
咱們知道: 當一個函數被定義時,它的scope屬性會指向他的父級的scope的引用 當一個函數執行時,會造成它本身的執行期上下文(AO),並把它掛載到他的做用域(scope chain)的最頂端,它的父級的scope依次下移。 當函數執行完畢後,他本身的執行期上下文(AO)會被銷燬 關於scope、AO詳情見理解js中的做用域異步
這樣,當咱們在函數內部返回一個函數並在其外部被一個變量接收時,它的做用域鏈上存的是它的父級的做用域鏈,只要這個函數存在則它的做用域鏈就會一直存在,這樣它的做用域鏈上的變量得不到釋放,即能在函數外部訪問做用域內部的變量。函數
爲了便於理解,咱們舉個簡單的例子:post
function test(){
var a = 100
function b(){
a++
console.log(a)
}
return b
}
var global = 100
var c = test()
c() // 101
c() // 102
複製代碼
1.當定義並執行test函數時,它的做用域鏈指向它的AO以及全局的GO 性能
2.當函數b被返回時,b的做用域鏈上掛載這它的父級的做用域鏈,即test執行時的做用域鏈 3.此時,雖然test()函數執行完畢,它的執行期上下文被銷燬( 注意這裏的銷燬指的是指向被銷燬,即圖中箭頭消失)。可是此時返回的函數b的scope上擁有b的父級的全部做用域鏈。此時,又將return 的b 賦值給c。因此,當執行c函數時,它會在b的做用域鏈上尋找所須要的變量,這樣就實現了閉包。因此當c執行兩次時,結果分別是10一、102閉包的做用通常有兩個: 1.能夠在函數外部,使用用函數內部的變量 2.函數內部的變量不會被釋放。ui
因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,this
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
data[1]();
data[2]();
複製代碼
輸出結果都是3。爲何?
1.當執行完for循環後,此時的全局執行期上下文爲
GO:{
data:[...],
i:3
}
複製代碼
2.當執行data[0]時,它產生它本身的執行期上下文(AO),此時他的做用域鏈爲
scope:[AO,GO]
複製代碼
此時,它的AO上沒有i變量,就向它的上一級的執行期上下文中找,即以上的GO,因此輸出結果爲3
其餘兩個執行結果同理。
當咱們將其修改成閉包時,即以下代碼
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = (function (j) {
return function(){
console.log(j);
}
})(i)
}
data[0](); // 0
data[1](); // 1
data[2](); // 2
複製代碼
此時,他的執行結果分別爲0,1,2
當執行完for循環時,此時的GO爲
GO:{
data:[...],
i:3
}
複製代碼
當data[0]執行時,此時他的做用域鏈爲
scope:[AO,匿名函數的AO,GO]
複製代碼
而此時匿名函數的AO爲
AO:{
i:0
}
複製代碼
data[0]的AO中沒有變量i,因此它沿着做用域鏈向上尋找,找到匿名函數的AO,即此時i爲0.
執行data[1],data[2]同理
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
console.log(i);
複製代碼
以上這道題是在面試中常常問到的問題,那麼它輸出的是什麼呢?相信大多數朋友均可以知道,最後他的結果爲 5,5,5,5,5,5。 只要瞭解了js的運行機制、以及同步異步的問題,咱們橫容易知道第一個5是當即輸出,以後的5在1s後同時輸出。
那麼咱們將它改造爲閉包。
for (var i = 0; i < 5; i++) {
(function(i){
setTimeout(function() {
console.log(i);
}, 1000);
})(i)
}
console.log(i);
複製代碼
它的結果爲5,0,1,2,3,4 咱們分析下它的做用域。
首先,先定義了5個當即執行函數,而後執行循環外部的console,此時的GO爲{i:5},因此先輸出5 1s後5個當即執行函數同時執行,此時定時器內部的i爲其外部函數(即當即執行函數)的i,此時i分別爲0,1,2,3,4,因此輸出爲5,0,1,2,3,4
想要那麼有沒有什麼其它方法來改造呢?答案是有的,es6裏提供了一個叫let的東西,他會造成塊級做用域。
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
console.log(i);
複製代碼
以上代碼會報錯,由於最後的i是不存在的,由於let造成了塊級做用域,只在for循環內部起做用。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); // The Window
複製代碼
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); // My Object
複製代碼
上面這兩道題,考察了閉包的用法以及this的指向問題,這裏就很少作解釋了。
相信看過個人這篇文章 關於js中的this指向問題,以後就能夠搞明白了。