關於在for迴圈中使用setTimeout
setTimeout的執行機制:
先將回調函式放到等待佇列中,等待區域內其他主程式執行完畢後,按時間順序先進先出執行回撥函式。本質上是作用域的問題。
在for迴圈中使用setTimeout時:
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); //6 6 6 6 6 6 }, i*1000 ); }
因setTimeout是非同步執行,每一次for迴圈的時候,setTimeout都執行一次,但是裡面的函式沒有被執行,而是被放到了任務佇列裡等待執行。只有主線上的任務執行完,才會執行任務佇列裡的任務。
即它會等到for迴圈全部執行完畢後,才會執行fun函式,當for迴圈結束後此時i的值已經變成了6,因此雖然定時器跑了5秒,控制檯上的內容依然是6。
另一種:
for (var i = 0; i < 5; i++) { (function() { // console.log(i); //0 1 2 3 4 // console.log(i * 1000); //0 1000 2000 3000 4000 setTimeout(function() { //在最後才執行,因為setTimeout是非同步載入 console.log(i);// 5 5 5 5 5 }, i * 1000); })(i);// console.log(i); //0 1 2 3 4 } console.log(i)//5 **重點
由setTimeout的執行機制可以知道,首先會執行外部的所有主程式,雖然for迴圈內形成了閉包,但是fun並沒有發現一個實參所以跟第一個例子並無實際差別,仍然是連續輸出5個5。
解決方法:
1、使用閉包
for (var i = 0; i < 5; i++) { (function(j) { setTimeout(function() { console.log(j);// 0 1 2 3 4 }, i * 1000); })(i);// console.log(i); //0 1 2 3 4 }
依次輸出0到4,因為實際引數跟定時器內部的 i 有強依賴。
通過閉包,將i的變數駐留在記憶體中,當輸出 j 時,引用的是外部函式的變數值i,i的值是根據迴圈來的,執行setTimeout時已經確定了裡面的的輸出了。
2、拆分結構
將setTimeout的的定義和呼叫放到不同的地方
function timer(i) { setTimeout( console.log( i ), i*1000 );// 1 2 3 4 5 } for (var i=1; i<=5;i++) { timer(i); }
3、使用es6中的let
for(let i=1;i<=5;i++){ setTimeout(functon(){ console.log(i); //1 2 3 4 5 },i*1000) }
因為for迴圈頭部的let不僅將 i 繫結到for迴圈中,事實上它將其重新繫結到迴圈體的每一次迭代中,確保上一次迭代結束的值被重新賦值。
setTimeout裡面的function()屬於一個新的域,通過var定義的變數是無法傳入到這個函式執行域中的,而使用let宣告塊變數能作用於這個塊 =》function就能使用 i 這個變量了
這個匿名函式的引數作用域和for引數的作用域不一樣,是利用了這一點來完成的。這個匿名函式的作用域有點類似類的屬性,是可以被內層方法使用的。
4、setTimeout第三個引數
for(var i= 1;i<=5;i++){ setTimeout(function(i){ console.log(i); // 1 2 3 4 5 }, i*1000, i ) }
由於每次傳入的引數都是從for迴圈中取到的值,所以依次輸出1-5。
這當然還是作用域的問題,但是在這裡setTimeout第三個引數卻把i的值給儲存了下來。這種解決方法比使用閉包輕快的多。
擴充套件:
setTimeout(function(x){ console.log(x) // 1 },1000,1) setTimeout(function(x,y){ console.log(x+y) // 3 },1000,1,2)
setTimeout的第三個引數起(可選引數)的作用就是第一個函式傳參。但是使用第三個引數需要注意的一點就是對IE9及以前的版本不相容。
轉載來源:https://www.cnblogs.com/wl0804/p/11987833.html