一、為什么會出現定時器不准確
這個其實就得提到js執行機制了,叫做事件循環Eventloop 循環機制中,異步事件 setInterval 到時后會把回調函數放入消息隊列中Event Queue,主線程的宏任務執行完畢后依次執行消息隊列的微任務,等微任務執行完了在循環回來執行宏任務。並且由於消息隊列中存在大量任務,其他任務執行時間就會造成定時器回調函數的延遲,如果不處理則會一直疊加延遲
二、宏任務和微任務
js可分為同步任務和異步任務,對於同步的任務,我們當然知道按照順序進行執行,但是對於異步的操作,會有一個優先級的執行順序,分別為宏任務和微任務
宏任務:setTimeout, setInterval, setImmediate, I/O, UI rendering
宏任務是一個外部腳本文件,一個用戶交互觸發的事件或一個setTimeout調用的回調函數。為了實現單線程這個概念,js有一個宏任務隊列(先進先出),宏任務不斷地創建出來塞到隊尾,js引擎不斷地從隊首取任務出來執行。
微任務:process.nextTick, Promises, MutationObserver
微任務是由Promise創建出來的且js中有一個專門的微任務隊列來存儲微任務。微任務的機制是:當執行完一個任務后,只要有微任務就先執行微任務。宏任務和渲染通通排到后面。

三、解決方法
根據定時器最開始時間計算當前時間(回調函數執行時間)與開始時間的誤差,用期望時差減誤差作為下一次任務的時間間隔
var startTime = new Date().getTime();
var count = 0;
//耗時任務
setInterval(function(){
var i = 0;
while(i++ < 100000000);
}, 0);
function handle() {
count++;
var offset = new Date().getTime() - (startTime + count * 1000);
var nextTime = 1000 - offset;
if (nextTime < 0) nextTime = 0;
setTimeout(handle, nextTime);
console.log(count + ' --- ' + (new Date().getTime() - (startTime + count * 1000)));
}
setTimeout(handle, 1000);
