前提知識
- JavaScript 是運行於單線程的環境中的,全部任務都在主線程上執行,造成一個執行棧(execution context stack)
- 除了主線程外,還有一個在進程空閒時執行的「代碼隊列」。隨着頁面在其生命週期中的推移,代碼會按照執行順序添加入隊列。另外,只要異步任務有告終果,回調函數的代碼就會被添加到隊列中。例如,當某個按鈕被按下時,它的事件處理程序代碼就會被添加到隊列。在 JavaScript 中沒有任何代碼是馬上執行的,但一旦進程空閒則儘快執行。
- 定時器對「任務隊列」的工做方式是,當特定時間過去後將代碼插入隊列。指定的時間間隔表示什麼時候將定時器的代碼添加到隊列中,而不是什麼時候實際執行代碼。
重複定時器(setIntervel)的缺點
當使用 setIntervel()時,僅當沒有該定時器的任何其餘代碼實例時,纔將定時器代碼添加到「任務隊列」。
這樣的機制會致使兩個問題:javascript
- 某些間隔會被跳過
- 多個定時器的代碼執行之間的間隔可能會比預期的小
Eg:假設某個 onclick 時間處理程序使用 setIntervel()設置了一個200ms間隔的重複定時器。若是事件處理程序須要花 300ms 的時間完成,定時器代碼也花了差很少的時間,例子代碼以下:java
btn.onclick = function() {
setIntervel(function() {
//...
//定時器代碼,耗時300ms多
}, 200); //每隔 200ms 將定時器代碼插入隊列
//...
//事件處理程序,耗時300ms
}

- 605ms處,第一個定時器代碼(在5ms處添加的)仍在運行,同時隊列中已經有了一個定時器代碼實例(在405ms時添加的),則605ms處本應被添加到隊列的定時器代碼不會被添加到隊列中。
- 在600ms多一點處,第一個定時器代碼(在5ms處添加的)結束以後,隊列中的定時器代碼(在405ms處添加的)就當即執行。而代碼的本意是隔200ms再執行下一個定時器代碼。
鏈式調用setTimeout()
setTimeout(function() {
//處理中
setTimeout(arguments.callee, interval)
}, interval)
這個模式的好處是:異步
- 在前一個定時器代碼執行完以前,不會把新的定時器代碼添加到代碼隊列,確保不會跳過任何間隔。
- 保證了在下一次定時器代碼執行以前,至少要等待指定的間隔,避免定時器代碼的連續運行。
Eg: 實現將一個 div 向右移動到 left 座標爲 200px 的動畫函數
setTimeout(function() {
var div = document.getElementById('myDiv');
var left = parseInt(div.style.left) + 5;
div.style.left = left + 'px';
if (left < 200) {
setTimeout(arguments.callee, 50);
}
}, 50)
參考資料
- 《JavaScript高級程序設計》做者: Nicholas C. Zakas