從閉包引出來的一系列問題

germo發表於2021-09-09

從閉包引出來的一系列問題

1. 不起眼的開始

for(var i = 0; i < 5; i++) {
    setTimeout(function() {        console.log(new Date, i)
    }, 1000)
}console.log(new Date, i)

很明顯,由於非同步的作用。到最後輸出的結果為6個5

如果用箭頭表示前後兩次輸出有1s的間隔,用,代表前後一起輸出,那麼輸出結果是5->5,5,5,5,5

這個也很容易的就可以進行解釋,先執行console.log(),再進行setTimeout()的非同步操作。

追問1:如果變成 5 -> 0,1,2,3,4 該怎樣處理?

首先可以使用閉包來解決這個問題:

for(var i = 0; i < 5; i++) {
    (function(j) {
        setTimeout(function() {            console.log(new Date, j)
        }, 1000)
    })(i)
}console.log(new Date, i) // 5

利用立即執行函式,來解決閉包造成的問題。

此外還可以使用setTimeout的第三個引數 :

for(var i = 0; i < 5; i++) {
    setTimeout(function(j) {        console.log(new Date, j)
    }, 1000, i)
}console.log(new Date, i) // 5

可能會有很多同學採用ES6的方式來避免:

for(let i = 0; i < 5; i++) {
    setTimeout(function() {        console.log(new Date, i)
    }, 1000)
}console.log(new Date, i)

但是此時並不能正確輸出我們想要的結果,因為let宣告的 i 產生了塊級作用域,導致 for 迴圈外面的輸出不能獲取的 i ,所以此時會報錯 i is not defined

追問2:如果把輸出變為 0->1->2->3->4->5 呢?

其中一種比較容易想到的方法:

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

setTimeout(function() {    console.log(new Date, i)
}, 1000*i)

但是js中定時器的觸發時機是不確定的,每次迴圈都會產生一個非同步操作之後,會有一個輸出,那麼完全可以使用Promise來解決這個問題。

const tasks = []for(var i = 0; i < 5; i++) {
    (function(j){
        tasks.push(new Promise((resolve) => {
            setTimeout(() => {                console.log(new Date, j)
                resolve()
            }, j*1000)
        }))
    })(i)
}Promise.all(tasks).then( () => {
    setTimeout( () => {        console.log(new Date, i)
    }, 1000)
})

將上面程式碼處理一下:

const tasks = []const output = function(i) {    new Promise( (resolve) => {
        setTimeout( () => {            console.log(new Date, i)
            resolve()
        }, 1000*i)
    })
}for(var i = 0;i < 5; i++) {
    tasks.push(output(i))
}// 全部Promise執行完畢,執行最後一個輸出iPromise.all(tasks).then( () => {
    setTimeout( () => {        console.log(new Date, i)
    }, 1000)
})

追問3:使用 async / await 怎麼實現

// 模擬sleepconst sleep = (time) => new Promise((resolve) => {
    setTimeout(resolve, time);
});(async () => {  // 宣告即執行的 async 函式表示式
    for (var i = 0; i < 5; i++) {        if (i > 0) {            await sleep(1000);
        }        console.log(new Date, i);
    }    await sleep(1000);    console.log(new Date, i);
})();



作者:宿雨jj
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1868/viewspace-2815139/,如需轉載,請註明出處,否則將追究法律責任。

相關文章