js經典面試問題:如何讓for循環中的setTimeout()函數像預想中一樣工作?

js經典面試問題:如何讓for循環中的setTimeout()函數像預想中一樣工作?
setTimeout()是js中的一類重要函數,將一段代碼延遲一定時間並異步執行。但是這個函數經常不聽話。在實踐中,可能經常有人碰到類似下面的這種情況:

for (var i = 1; i <= 2; i++) {
setTimeout(function() { alert(i) }, 100);
}

我們期望的結果是,先隔100毫秒彈出1,再隔100毫秒彈出2。但是跑起來後,alert的兩次內容都是數字3,而且緊挨着輸出,並不是自己所期望的先1後2。有一種很基礎的面試題是,如何合理改動代碼,使它返回期望的結果?

其實很簡單。在stackoverflow上早有大神解釋了,可以參考這個鏈接

答案翻譯成中文如下(並做了部分修改方便理解):


你要爲每個定時器處理函數創建不同的“i”變量副本。比如這樣:

複製代碼
function doSetTimeout(i) {
setTimeout(function() { alert(i); }, 100);
}

for (var i = 1; i <= 2; ++i)
doSetTimeout(i);
複製代碼

如果你不做這樣的事情 (這種方法在實際上也會有其他變種),每個定時器處理函數就會共享同一作用域裏的同一變量"i"。當循環完成時的"i"是多少?是3!這裏通過定義一個函數來實現中介的作用,從而創建了變量的值的副本。由於setTimeout()是在該副本的上下文中調用的,所以它每次都有自己的私有的"i"以供使用。

但是隨着時間的推移,這些代碼的效果顯得有些混亂是顯而易見的事實,因爲設立一些時間間隔相同的連續的setTimeout()將導致所有延時處理程序同時被調用。瞭解設置timer(對setTimeout()的調用)幾乎不消耗時間是很重要的。也就是說,告訴系統“請在1000毫秒後調用此函數”將會被立即返回,因爲在timer隊列中安裝延時請求的過程非常快。

因此,如果有一串連續的延時請求(比如我答案中的代碼),而且每一個時間延遲值是相同的,那麼一旦經過足夠的時間,所有延時處理程序將一個接一個快速連續調用。

如果你需要的是在固定時間間隔調用的處理程序,你可以使用setInterval(),行爲非常類似setTimeout(),但每經過一定時間就運行一次。或者你可以調用以“時間值乘以迭代計數器”爲時間間隔的setTimeout。也就是說,修改我剛纔的示例代碼:

function doScaledTimeout(i) {
setTimeout(function() {
alert(i);
}, i * 5000);
}

(100毫秒超時,效果不會很明顯,所以我設置的數字高達5000)

“i”值乘以基礎延遲值,所以循環5次將導致分別延遲5秒,10秒,15秒,20秒,和25秒。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章