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秒。