javascript之溫習閉包

Alex丶Cheng發表於2019-02-14

閉包的定義:閉包是由函式和函式能夠訪問的自由變數所組成,即使建立它的上下文已經被銷燬,它依然存在。

  1. 最經典的例子:
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)尋找了。

相關文章