你不知道的javascript--作用域閉包-3

聲明:以下內容摘自《你不知道的javascript》上卷一書的第5章的片段。

1.循環和閉包

要說明閉包,for循環是最常見的例子。

for (var i=1; i <= 5; i++) {
    setTimeout( function timer() {
        console.log( i );
    }, i*1000);
}

正常情況下,我們隊這段代碼行為的預期是分別輸出數字1~5,每秒一次,每次一個。

但實際上,這段代碼在運行時會 以每秒一次的頻率輸出五次6

為什么???

首先解釋6是從哪里來的。這個循環的終止條件是i不再<=5。條件首次成立時,i的值是6。因此,輸出顯示的是循環結束時i的最終值。

延遲函數的回調會在循環結束時才執行。

事實上,當定時器運行時,即使每個迭代中執行的是setTimeout(..., 0),所有的回調函數依然是在循環結束后才會被執行,因為會每次輸出一個6。

這里引申出一個更深入的問題,代碼中到底有什么 缺陷 導致它的 行為同語義 所暗示的不一致?

缺陷是我們試圖假設循環中的每個迭代在運行時都會給自己“捕獲”一個i的副本。
但是根據作用域的工作原理,實際情況是盡管循環中的五個函數是在各個迭代中分別定義的,但它們都 被封閉在一個共享的全局作用域中,因此實際上只有一個i(i在全局作用域中)。

即所有函數共享一個 i 的引用。

應對缺陷,我們需要更多的閉包作用域,特別是在循環的過程中每個迭代都需要一個閉包作用域。

上一節介紹過,IIFE會通過聲明并立即執行一個函數來創建作用域。

for (var i=1; i <= 5; i++) {
    (function() {
        setTimeout( function timer() {
            console.log( i );
        }, i*1000);
    })();
}

這樣能行嗎?試試!

。。。這樣不行。為什么呢?
我們現在顯然擁有更多的詞法作用域了。的確每個延遲函數都會將IIFE在每次迭代中創建的作用域封閉起來。

如果作用域是空的,那么僅僅將它們進行封閉是不夠的。
仔細看一下,我們的IIFE只是一個什么都沒有的空作用域。它需要包含一點實質內容才能為我們所用。

它需要有自己的變量,用來在每個迭代中儲存 i 的值:

for (var i=1; i <= 5; i++) {
    (function() {
        var j = i;
        setTimeout( function timer() {
            console.log( j );
        }, j*1000);
    })();
}

行了!!!它能正常工作了!!!

對上述代碼做一點改進:

for (var i=1; i <= 5; i++) {
    (function(j) {
        setTimeout( function timer() {
            console.log( j );
        }, j*1000);
    })(i);
}

當然,這些IIFE也不過就是函數,因此我們可以將i傳遞進去,如果愿意的話可以將變量名定位j,當然也可以還叫i。

在迭代內使用IIFE會為每個迭代都生成一個新的作用域,使得延遲函數的回調可以將新的作用域封閉在每個迭代內部,每個迭代中都會含有一個具有正確值的變量供我們訪問。

重返塊作用域

仔細思考我們對前面的解決方案的分析。

我們使用IIFE在每次迭代時都創建一個新的作用域。換句話說,每次迭代我們都需要一個塊作用域

ES6中,let聲明,可以用來劫持塊作用域,并且在這個塊作用域中聲明一個變量。

本質上這是將一個塊轉換成一個可以被關閉的作用域。

cool codes!

for (var i=1; i <= 5; i++) {

    let j = i; // ding! 閉包的塊作用域

    setTimeout( function timer() {
        console.log( j );
    }, j*1000);

}

但是,這還不是全部!
for 循環頭部的 let 聲明還會有一個特殊的行為。這個行為指出變量在循環過程中不僅僅被聲明一次,每次迭代都會聲明!隨后的每個迭代都會使用上一個迭代結束時的值來初始化這個變量。

for (let i=1; i <= 5; i++) {

    setTimeout( function timer() {
        console.log( i );
    }, i*1000);

}

Cool, right???

塊作用域和閉包聯手便所向披靡。反正這個功能讓我成為了一名快樂的javascript程序員。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容