我們先來簡單了解一下setTimeout延時器的運行機制,setTimeout會先將回呼函式放到等待佇列中,等待區域內其他主程式執行完畢后,按時間順序先進先出執行回呼函式,本質上是作用域的問題,
因此若是這樣將不會得到想要的結果輸出1.2.3.4.5,而會連續輸出5個6,
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
這是因為setTimeout是異步執行,每一次for回圈的時候,setTimeout都執行一次,但是里面的函式沒有被執行,而是被放到了任務佇列里,等待執行,只有主線上的任務執行完,才會執行任務佇列里的任務,也就是說它會等到for回圈全部運行完畢后,才會執行fun函式,但是當for回圈結束后此時i的值已經變成了6,因此雖然定時器跑了5秒,控制臺上的內容依然是6,
(注意:for回圈從開始到結束的程序,需要維持幾微秒或幾毫秒,當定時器跑完一秒之后for回圈早已經做完了,)
我們來看另一種情況:
for (var i=1; i<=5; i++) { (function() { setTimeout( function timer() { console.log( i ); }, i*1000 ); })(); }
由setTimeout的運行機制可以知道,首先會運行外部的所有主程式,雖然for回圈內形成了閉包,但是fun并沒有發現一個實參所以跟第一個例子并無實際差別,仍然是連續輸出5個6,
解決方案1:閉包
使用閉包是很經典的一種做法:
for (var i=1; i<=5; i++) { (function(j) { setTimeout( function timer() { console.log( j ); }, j*1000 ); })(i); }
我們可以發現跟預期結果一致,依次輸出1到5,因是因為實際引數跟定時器內部的i有強依賴,
通過閉包,將i的變數駐留在記憶體中,當輸出j時,參考的是外部函式的變數值i,i的值是根據回圈來的,執行setTimeout時已經確定了里面的的輸出了,
解決方案2:拆分結構
我們還可以將setTimeout的定義和呼叫分別放到不同部分:
function timer(i) { setTimeout( console.log( i ), i*1000 ); } for (var i=1; i<=5;i++) { timer(i); }
控制臺上輸出依然是依次輸出1到5,
解決方案3:let
這里再來說一說使用es6的let來解決此問題:
for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
這個例子與第一個相比,只是把var更改成了let,可是控制臺的結果卻是依次輸出1到5,
因為for回圈頭部的let不僅將i系結到for回圈中,事實上它將其重新系結到回圈體的每一次迭代中,確保上一次迭代結束的值重新被賦值,setTimeout里面的function()屬于一個新的域,通過var定義的變數是無法傳入到這個函式執行域中的,通過使用let來宣告塊變數能作用于這個塊,所以function就能使用i這個變數了;這個匿名函式的引數作用域和for引數的作用域不一樣,是利用了這一點來完成的,這個匿名函式的作用域有點類似類的屬性,是可以被內層方法使用的,
解決方案4:setTimeout第三個引數
for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000, i ); }
由于每次傳入的引數是從for回圈里面取到的值,所以會依次輸出1到5,關于setTimeout第三個引數,下一篇會詳細講到,這里大家了解下就好,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/165620.html
標籤:JavaScript
