解決js定時器不准的問題


為什么會出現定時器不准呢?

這個就得從js的執行機制說起了,在事件循環(EventLoop)執行機制中,異步事件(setInterval/setTimeout)會把回調函數放入消息隊列(Event Queue)中,主線程的宏任務執行完畢后,依次執行消息隊列中的微任務,等微任務執行完了再循環回來執行宏任務。由於消息隊列中存在大量的任務,其他任務的執行時間就會造成定時器回調函數的延遲,如果不處理,就會一直疊加延遲,當運行時間久了之后,相差就會很大。

因此定時器是不能完全保證的。

解決方案

1. 動態計算時差(僅針對循環定時器起到修正作用)

在定時器開始前和在運行時動態獲取當前時間戳,在設置下一次定時時長時,在期望值的基礎上減去當前差值,以獲取相對精確的定時器運行效果

此方法僅能消除setInterval長時間運行造成的誤差,或者setTimeout循環長時間運行的累計誤差,無法對當個定時器消除執行的延遲

// 每秒倒計時的實現
let startTime, // 開始時間
  count, // 計數器
  runTime, // 當前時間
  downSecond = 1200,  // 倒計時時間
  loopTimer = null;

function resetDefaultValue() {

  startTime = Date.now();
  count = 0;
  runTime = 0;
}

resetDefaultValue(); //每次倒計時執行前要重置一下初始值
loop();
function loop() {

  runTime = Date.now();
  let offsetTime = runTime - (startTime + count * 1000); //時間差
  count++;
  let nextTime = 1000 - offsetTime; //下一次定時器需要的時間
  nextTime = nextTime > 0 ? nextTime : 0;
  downSecond-- ;
  // 處理邏輯區域 ---- s
  console.log('時間差:'+offsetTime, ',下一次需要時間:'+ nextTime)
  if (downSecond <= 0) {

    // 結束定時器
    clearTimeout(loopTimer)
    loopTimer = null;
    return false;
  }
  // 處理邏輯區域 ---- e
  loopTimer = setTimeout(loop, nextTime);
}

 

 

 

var count = count2 = 0;
 var runTime,runTime2;
 var startTime,startTime2 = performance.now();//獲取當前時間
 
 //普通任務-對比
 setInterval(function(){
     runTime2 = performance.now();
     ++count2;    
     console.log("普通任務",count2 + ' --- 延時:' + (runTime2 - (startTime2 + count2 * 1000)) + ' 毫秒');
 }, 1000);
 
 //動態計算時長
 function func(){
  runTime = performance.now();
     ++count;
     let time = (runTime - (startTime + count * 1000));
     console.log("優化任務",count2 + ' --- 延時:' + time +' 毫秒');
     //動態修正定時時間
     t = setTimeout(func,1000 - time);
 }
 startTime = performance.now();
 var t = setTimeout(func , 1000);
 
 //耗時任務
 setInterval(function(){
     let i = 0;
     while(++i < 100000000);
 }, 0);

 

 

 從上面看出,不管是setTimeout還是setInterval,在長時間運行中,都會存在誤差,而修正就是將定時器拉會原來的軌道

2. 使用web worker

Web Worker 的作用,就是為 JavaScript 創造多線程環境,允許主線程創建 Worker 線程,將一些任務分配給后者運行。在主線程運行的同時,Worker 線程在后台運行,兩者互不干擾。等到 Worker 線程完成計算任務,再把結果返回給主線程。這樣的好處是,一些計算密集型或高延遲的任務,被 Worker 線程負擔了,主線程(通常負責 UI 交互)就會很流暢,不會被阻塞或拖慢。

<html>
<meta charset="utf-8">
<body>
<script type="text/javascript">
var count = 0;
var runTime;

//performance.now()相對Date.now()精度更高,並且不會受系統程序堵塞的影響。
//API:https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now
var startTime = performance.now(); //獲取當前時間

//普通任務-對比測試
setInterval(function(){
    runTime = performance.now();
    ++count;    
    console.log("普通任務",count + ' --- 普通任務延時:' + (runTime - (startTime + 1000))+' 毫秒');
    startTime = performance.now();
}, 1000);

//耗時任務
setInterval(function(){
    let i = 0;
    while(i++ < 100000000);
}, 0);

// worker 解決方案
let worker = new Worker('worker.js');
</script>
</body>
</html>
// worker.js
var count = 0;
var runTime;
var startTime = performance.now();
setInterval(function(){
   runTime = performance.now();
   ++count;
   console.log("worker任務",count + ' --- 延時:' + (runTime - (startTime + 1000))+' 毫秒');
   startTime = performance.now();
}, 1000);

 

 可以看到使用worker后,延遲會非常小,基本上在3毫秒內,而且worker任務不受其他任務的干擾,即使瀏覽器進入后台,也沒有影響worker

使用web worker要注意以下幾點

(1)同源限制

分配給 Worker 線程運行的腳本文件,必須與主線程的腳本文件同源。

(2)DOM 限制

Worker 線程所在的全局對象,與主線程不一樣,無法讀取主線程所在網頁的 DOM 對象,也無法使用document、window、parent這些對象。但是,Worker 線程可以navigator對象和location對象。

(3)通信聯系

Worker 線程和主線程不在同一個上下文環境,它們不能直接通信,必須通過消息完成。

(4)腳本限制

Worker 線程不能執行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 對象發出 AJAX 請求。

(5)文件限制

Worker 線程無法讀取本地文件,即不能打開本機的文件系統(file://),它所加載的腳本,必須來自網絡。

 參考:

https://johnresig.com/blog/how-javascript-timers-work/

https://blog.csdn.net/qq_41494464/article/details/99944633

http://www.ruanyifeng.com/blog/2018/07/web-worker.html 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM