寫在前邊:
我們知道,當函式執行時,會形成自己的執行期上下文,並把它掛到自己的作用域鏈上,當函式執行完之後,它的執行期上下文就會被釋放。所以,一般情況下,在函式外部訪問函式內部的變數需要特殊的方法才能解決,這個特殊的方法就是閉包。
在理解閉包前,我建議你先了解下js的作用域。
理解js中的作用域
閉包的概念
閉包:閉包指的是在函式的外部可以訪問函式內部的變數。函式沒有被釋放,整條作用域鏈上的區域性變數都將得到保留。建立閉包的一般方法是在函式內部返回乙個新函式。
通俗的理解:
閉包:顧名思義,是乙個封閉的包,但是這個包露出內部的一條線,這條線就是閉包內部返回函式的作用域鏈,它上面掛載了這個函式以及他的所有父級函式的變數,我們可以通過這條線訪問到函式內的變數,這就是閉包。
閉包的形成機制
我們知道:
當乙個函式被定義時,它的scope屬性會指向他的父級的scope的引用
當乙個函式執行時,會形成它自己的執行期上下文(ao),並把它掛載到他的作用域(scope chain)的最頂端,它的父級的scope依次下移。
當函式執行完畢後,他自己的執行期上下文(ao)會被銷毀
關於scope、ao詳情見理解js中的作用域
這樣,當我們在函式內部返回乙個函式並在其外部被乙個變數接收時,它的作用域鏈上存的是它的父級的作用域鏈,只要這個函式存在則它的作用域鏈就會一直存在,這樣它的作用域鏈上的變數得不到釋放,即能在函式外部訪問作用域內部的變數。
為了便於理解,我們舉個簡單的例子:
function test()
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執行兩次時,結果分別是101、102
閉包的作用
閉包的作用一般有兩個:
1.可以在函式外部,使用用函式內部的變數
2.函式內部的變數不會被釋放。
閉包的缺陷
由於閉包會使得函式中的變數都被儲存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,
關於閉包的常見面試題
第一題:
var data = ;
for (var i = 0; i < 3; i++) ;
}data[0]();
data[1]();
data[2]();
輸出結果都是3。為什麼?
1.當執行完for迴圈後,此時的全域性執行期上下文為
go:
2.當執行data[0]時,它產生它自己的執行期上下文(ao),此時他的作用域鏈為
scope:[ao,go]
此時,它的ao上沒有i變數,就向它的上一級的執行期上下文中找,即以上的go,所以輸出結果為3
其他兩個執行結果同理。
當我們將其修改為閉包時,即如下**
var data = ;
for (var i = 0; i < 3; i++)
})(i)
}data[0](); // 0
data[1](); // 1
data[2](); // 2
此時,他的執行結果分別為0,1,2
當執行完for迴圈時,此時的go為
go:
當data[0]執行時,此時他的作用域鏈為
scope:[ao,匿名函式的ao,go]
而此時匿名函式的ao為
ao:
data[0]的ao中沒有變數i,所以它沿著作用域鏈向上尋找,找到匿名函式的ao,即此時i為0.
執行data[1],data[2]同理
第二題
for (var i = 0; i < 5; i++) , 1000);
}console.log(i);
以上這道題是在面試中經常問到的問題,那麼它輸出的是什麼呢?相信大多數朋友都可以知道,最後他的結果為
5,5,5,5,5,5。
只要了解了js的執行機制、以及同步非同步的問題,我們橫容易知道第乙個5是立即輸出,之後的5在1s後同時輸出。
那麼我們將它改造為閉包。
for (var i = 0; i < 5; i++) , 1000);
})(i)
}console.log(i);
它的結果為5,0,1,2,3,4
我們分析下它的作用域。
首先,先定義了5個立即執行函式,然後執行迴圈外部的console,此時的go為,所以先輸出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++) , 1000);
}console.log(i);
以上**會報錯,因為最後的i是不存在的,因為let形成了塊級作用域,只在for迴圈內部起作用。 深入理解JS閉包
閉包 closure 是j acript語言的乙個難點,也是它的特色,很多高階應用都要依靠閉包實現。一 變數的作用域 要理解閉包,首先必須理解j ascript特殊的變數作用域。變數的作用域無非就是兩種 全域性變數和區域性變數。j ascript語言的特殊之處,就在於函式內部可以直接讀取全域性變數。...
深入理解js閉包(快速理解)
一 閉包的基本概念 閉包是函式的一種高階應用方式 通過建立乙個不被銷毀的儲存空間,來在函式的外部呼叫和使用函式內部的資料二 如何定義乙個不被銷毀的執行空間 預設情況下,函式執行完畢,函式執行空間中的程式,也會被銷毀 定義不被銷毀的執行空間的方法 1,在函式內部,返回乙個引用資料型別 陣列,物件,函式...
深入理解閉包
閉包的定義 mdn 對閉包的定義 根據上面的例子,舉乙個例子 var str xiaoqi function getname getname 函式可以返回str這個變數,但str即不是getname函式的區域性變數,也不是foo函式的引數,所以str就是自由變數。這樣函式getname就是乙個閉包。...