閉包的定義:閉包是由函式和函式能夠訪問的自由變數所組成,即使建立它的上下文已經被銷燬,它依然存在。
- 最經典的例子:
for (var i = 0; i < 10; i ++) {
setTimeout(function () {
console.log(i)
}, i * 1000)
}
複製程式碼
- 毫無疑問,我們都知道這裡將會每隔1s輸出一個10,為什麼呢?我們可以利用Event Loop來解釋這一現象。當程式開始執行的時候,同步和非同步任務分別進入不同的執行"場所",同步的進入主執行緒,非同步的進入Event Table並註冊函式。在這裡,for會進入主程式開始執行,而setTimeout進入Event Table ,並註冊回撥函式。由於js引擎存在monitoring process程式,會持續不斷的檢查主執行緒執行棧是否為空,一旦為空,就會去Event Queue那裡檢查是否有等待被呼叫的函式。而此時,for迴圈已經執行完畢,此時的i成為全域性變數,Event Queue中的函式訪問到的變數i均為10
解決方法有很多,比如使用IIFE,或者ES6中的let來建立塊級作用域。這裡列出一個通過js值型別的方法來決定這個問題:
function func (i) {
setTimeout(function() {
console.log(i)
}, i * 1000)
}
for (var i = 0; i < 10; i ++) {
func(i)
}
複製程式碼
最後,我們來分析一下下面這道面試必刷題
var arr = []
for (var i = 0 ; i < 5; i ++) {
arr[i] = (function(j) {
return function() {
console.log(j)
}
})(i)
}
arr[0]() // 0
arr[1]() // 1
arr[2]() // 2
...
複製程式碼
- 這裡我們是通過IIFE來實現往陣列中新增值的,陣列的每一項值都是一個函式,呼叫函式,列印出迴圈變數 i
- 當執行到
arr[0]()
時,函式 arr[0]
的作用域連結串列示如下 :
// AO 表示活動物件, VO表示變數物件,其實它倆是等價的
函式上下文 [AO, anonymous(匿名函式)context.VO,Global(全域性)context.VO]
複製程式碼
- 執行函式
arr[0]()
的時候,首先函式會到自己的AO中尋找是否存在變數 i,沒有找到就會去匿名函式中的VO尋找變數 i (i = 0),此時,通過閉包,我們將每次迴圈的變數i 都存在其上下文中,能夠訪問到,便輸出 i,並不再向GlobalContext中的VO( i 的值為 5)尋找了。