從閉包引出來的一系列問題
1. 不起眼的開始
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i)
}, 1000)
}
console.log(new Date, i)
很明顯,由于異步的作用。到最后輸出的結(jié)果為6個5
如果用箭頭表示前后兩次輸出有1s的間隔,用,代表前后一起輸出,那么輸出結(jié)果是5->5,5,5,5,5
這個也很容易的就可以進(jìn)行解釋,先執(zhí)行console.log(),再進(jìn)行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
利用立即執(zhí)行函數(shù),來解決閉包造成的問題。
此外還可以使用setTimeout的第三個參數(shù) 文檔:
for(var i = 0; i < 5; i++) {
setTimeout(function(j) {
console.log(new Date, j)
}, 1000, i)
}
console.log(new Date, i) // 5
可能會有很多同學(xué)采用ES6的方式來避免:
for(let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i)
}, 1000)
}
console.log(new Date, i)
但是此時并不能正確輸出我們想要的結(jié)果,因為let聲明的 i 產(chǎn)生了塊級作用域,導(dǎo)致 for 循環(huán)外面的輸出不能獲取的 i ,所以此時會報錯 i is not defined
追問2:如果把輸出變?yōu)?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中定時器的觸發(fā)時機(jī)是不確定的,每次循環(huán)都會產(chǎn)生一個異步操作之后,會有一個輸出,那么完全可以使用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執(zhí)行完畢,執(zhí)行最后一個輸出i
Promise.all(tasks).then( () => {
setTimeout( () => {
console.log(new Date, i)
}, 1000)
})
追問3:使用 async / await 怎么實現(xiàn)
// 模擬sleep
const sleep = (time) => new Promise((resolve) => {
setTimeout(resolve, time);
});
(async () => { // 聲明即執(zhí)行的 async 函數(shù)表達(dá)式
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);
})();