for 迴圈中的setTimeout(function(){})非同步問題
阿新 • • 發佈:2019-01-01
閱讀這段程式碼
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000)
}
預期是分別輸出數字1-5, 每秒1次,每次1個
結果卻是每秒一次輸出了5個6
1. setTimeout的執行原理
setTimeout()是一個非同步方法, 傳遞一個函式,
延遲一段時候把該函式新增到隊列當中,並不是立即執行,
而且必須等當前環境所有程式碼執行完以後, 才會執行
也就是說我們執行這個for迴圈的時候
setTimeout(fun(...), 1000)
setTimeout(fun(...), 2000)
setTimeout(fun(...), 3000)
setTimeout(fun(...), 4000)
setTimeout(fun(...), 5000)
五個函式先進入了佇列, 然後等for迴圈結束後再依次出隊 (粗略理解, 實質上是回撥函式)
for迴圈結束後, 此時i是等於6的, 所以每秒一次輸出了5個6
要注意到var定義的i實質上是全域性變數, 等同於下面的程式碼
var i
for (i = 1; i <= 5; i++ ) {
...
}
2. 解決辦法
其實還是詞法作用域的問題,我們從作用域下手,解決這個問題
2.1 使用閉包
這個辦法的原理是建立了閉包作用域, 每次迴圈會生成一個新的閉包作用域, 使得延遲函式回撥可以訪問到正確的值
注意, 閉包作用域裡必須宣告變數j, 如果是一個空的作用域, 那不會產生作用
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console. log(j);
}, j*1000)
})(i)
}
2.2 使用ES6中的let
這個辦法的原理是通過let來劫持塊作用域, 注意, 這個變數i不只會宣告一次, 每次迭代的時候都會宣告i, 每次迭代後, i的值都會使用
上一個迭代的值來初始化這個變數, 形成一個塊作用域
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000)
}