自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

字節(jié)面試官問粉絲,如何實現(xiàn)準(zhǔn)時的SetTimeout

開發(fā) 開發(fā)工具
想得到準(zhǔn)確的,我們第一反應(yīng)就是如果我們能夠主動去觸發(fā),獲取到最開始的時間,以及不斷去輪詢當(dāng)前時間,如果差值是預(yù)期的時間,那么這個定時器肯定是準(zhǔn)確的,那么用 while 可以實現(xiàn)這個功能。

[[394615]]

最近一個粉絲去面字節(jié),被面試官問到了這個問題來問我,一聽感覺有點(diǎn)意思,于是對它進(jìn)行了一番研究,可能研究的過程以及結(jié)果不一定是最好的,但是還是記錄一下,為各位提供一些幫助。

拿到這個問題,假設(shè)有這樣的場景,我們需要用 setTimeout 做一個動畫,并且需要控制他的頻率,50ms 運(yùn)行一次,首先我們先上圖,來看看 setTimeout 的表現(xiàn)。

運(yùn)行代碼如下,通過一個計數(shù)器來記錄每一次 setTimeout 的調(diào)用,而設(shè)定的間隔 * 計數(shù)次數(shù),就等于理想狀態(tài)下的延遲,通過以下例子來查看我們計時器的準(zhǔn)確性。

  1. function timer() { 
  2.    var speed = 50, // 設(shè)定間隔 
  3.    counter = 1,  // 計數(shù) 
  4.    start = new Date().getTime(); 
  5.     
  6.    function instance() 
  7.    { 
  8.     var ideal = (counter * speed), 
  9.     real = (new Date().getTime() - start); 
  10.      
  11.     counter++; 
  12.     form.ideal.value = ideal; // 記錄理想值 
  13.     form.real.value = real;   // 記錄真實值 
  14.  
  15.     var diff = (real - ideal); 
  16.     form.diff.value = diff;  // 差值 
  17.  
  18.     window.setTimeout(function() { instance(); }, speed); 
  19.    }; 
  20.     
  21.    window.setTimeout(function() { instance(); }, speed); 
  22. timer(); 

而我們?nèi)绻?setTimeout 還未執(zhí)行期間加入一些額外的代碼邏輯,再來看看這個差值。

  1. ... 
  2. window.setTimeout(function() { instance(); }, speed); 
  3. for(var x=1, i=0; i<10000000; i++) { x *= (i + 1); } 
  4. ... 

可以看出,這大大加劇了誤差。

可以看到隨著時間的推移, setTimeout 實際執(zhí)行的時間和理想的時間差值會越來越大,這就不是我們預(yù)期的樣子。類比真實的場景,對于一些倒計時以及動畫來說都會造成時間的偏差都是不理想的。

那么,從這個現(xiàn)象來看一下,為什么 setTimeout 會不準(zhǔn)時呢?

因為我們的代碼往往并不是只有一個 setTimeout,大多數(shù)會遇到以下情況。

詳細(xì)要從瀏覽器的事件循環(huán)講起,但是講事件循環(huán)的文章太多了,文本就不再累贅地詳細(xì)展開講解。

視頻

  • https://www.youtube.com/watch?v=8aGhZQkoFbQ

(國內(nèi)視頻 https://www.bilibili.com/video/av456657611/)

建議看國外的中英對照字幕,國內(nèi)的翻譯準(zhǔn)確度一般

相關(guān)文章

  • https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

總結(jié)來說,因為瀏覽器頁面是有消息隊列和事件循環(huán)來驅(qū)動的,創(chuàng)建一個 setTimeout 的時候是將它推進(jìn)了一個隊列,并沒有立即執(zhí)行,只有本輪宏任務(wù)執(zhí)行完,才會去檢查當(dāng)前的消息隊列是否有有到期的任務(wù)。

接下來我會用 4 這種方式來探索。

while

想得到準(zhǔn)確的,我們第一反應(yīng)就是如果我們能夠主動去觸發(fā),獲取到最開始的時間,以及不斷去輪詢當(dāng)前時間,如果差值是預(yù)期的時間,那么這個定時器肯定是準(zhǔn)確的,那么用 while 可以實現(xiàn)這個功能。

理解起來也很簡單:

代碼如下:

  1. function timer(time) { 
  2.     const startTime = Date.now(); 
  3.     while(true) { 
  4.         const now = Date.now(); 
  5.         if(now - startTime >= time) { 
  6.             console.log('誤差', now - startTime - time); 
  7.             return
  8.         } 
  9.     } 
  10. timer(5000); 

打?。赫`差 0

顯然這樣的方式很精確,但是我們知道 js 是單線程運(yùn)行,使用這樣的方式強(qiáng)行霸占線程會使得頁面進(jìn)入卡死狀態(tài),這樣的結(jié)果顯然是不合適的。

Web Worker

那么既然無法在當(dāng)前主線程避免這個誤差,我們能否另開一個線程去處理呢?當(dāng)然可以,JavaScript 也提供給我們這樣一個能力,通過 Web Worker 我們就可以在另一個線程來運(yùn)行我們的代碼。

Web Worker為Web內(nèi)容在后臺線程中運(yùn)行腳本提供了一種簡單的方法。線程可以執(zhí)行任務(wù)而不干擾用戶界面。 -- 摘自MDN

一個 worker 的簡單的示例

  1. // main.js 
  2. var myWorker = new Worker('worker.js'); 
  3.  
  4. // 監(jiān)聽 worker 
  5. myWorker.onmessage = function(e) { 
  6.   result.textContent = e.data; 
  7.   console.log('Message received from worker'); 
  8. first.onchange = function() { 
  9.   // 向 worker 發(fā)送數(shù)據(jù) 
  10.   myWorker.postMessage([first.value,second.value]); 
  11.   console.log('Message posted to worker'); 
  1. // worker.js 
  2. onmessage = function(e) { 
  3.   // 接受主線程的數(shù)據(jù) 
  4.   console.log('Message received from main script'); 
  5.   var workerResult = 'Result: ' + (e.data[0] * e.data[1]); 
  6.   console.log('Posting message back to main script'); 
  7.   // 向主線程發(fā)送數(shù)據(jù) 
  8.   postMessage(workerResult); 

那么接下來我們就要加 worker 和 while 相結(jié)合,以下為創(chuàng)建 worker 部分

  1. // worker生成器 
  2. const createWorker = (fn, options) => { 
  3.     const blob = new Blob(['(' + fn.toString() + ')()']); 
  4.     const url = URL.createObjectURL(blob); 
  5.     if (options) { 
  6.         return new Worker(url, options); 
  7.     } 
  8.     return new Worker(url); 
  9. }  
  10. // worker 部分 
  11. const worker = createWorker(function () { 
  12.     onmessage = function (e) { 
  13.         const date = Date.now(); 
  14.         while (true) { 
  15.             const now = Date.now(); 
  16.             if(now - date >= e.data) { 
  17.                 postMessage(1); 
  18.                 return
  19.             } 
  20.         } 
  21.     } 
  22. }) 

我們通過在 worker 中寫入一個 while 循環(huán),當(dāng)達(dá)到我們的預(yù)取時間的時候,再向主線程發(fā)送一個完成事件,就不會因為主線程的其他代碼的干擾而造成數(shù)據(jù)不準(zhǔn)的情況。

  1. let isStart = false
  2. function timer() { 
  3.     worker.onmessage = function (e) { 
  4.        cb() 
  5.         if (isStart) { 
  6.             worker.postMessage(speed); 
  7.         }  
  8.     } 
  9.     worker.postMessage(speed); 

我們來看一下實際的效果。

我們可以看到執(zhí)行的時間和理想的時間非常相近,而那細(xì)微的差異應(yīng)該就是線程通訊耗時。

我們再來看看加入額外的代碼邏輯的情況。

  1. ... 
  2. if (isStart) { 
  3.    worker.postMessage(speed); 
  4. for (var x = 1, i = 0; i < 10000000; i++) { x *= (i + 1); } 
  5. ... 

![](https://s3.qiufengh.com/blog/2021-04-20 23.16.44.gif)

時間明顯增加了一些,但是增加速度非常緩慢。

雖然我們用 Web Worker 修復(fù)時間看似被解決了。但是一方面, worker 線程會被 while 給占用,導(dǎo)致無法接受到信息,多個定時器無法同時執(zhí)行,另一方面,由于 onmessage 還是屬于事件循環(huán)內(nèi),如果主線程有大量阻塞還是會讓時間越差越大,因此這并不是個完美的方案。

requestAnimationFrame

先來看看他的定義

window.requestAnimationFrame() 告訴瀏覽器——你希望執(zhí)行一個動畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動畫。該方法需要傳入一個回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會在瀏覽器下一次重繪之前執(zhí)行,回調(diào)函數(shù)執(zhí)行次數(shù)通常是每秒60次,也就是每16.7ms 執(zhí)行一次,但是并不一定保證為 16.7 ms。

我們也可以嘗試一下將它來模擬 setTimeout。

  1. // 模擬代碼 
  2. function setTimeout2 (cb, delay) { 
  3.     let startTime = Date.now() 
  4.     loop() 
  5.    
  6.     function loop () { 
  7.       const now = Date.now() 
  8.       if (now - startTime >= delay) { 
  9.         cb(); 
  10.         return
  11.       } 
  12.       requestAnimationFrame(loop) 
  13.     } 

發(fā)現(xiàn)由于 16.7 ms 間隔執(zhí)行,在使用間隔很小的定時器,很容易導(dǎo)致時間的不準(zhǔn)確。

再看看額外代碼的引入效果。

  1. ... 
  2.  window.setInterval2(function () { instance(); }, speed); 
  3. for (var x = 1, i = 0; i < 10000000; i++) { x *= (i + 1); } 
  4. ... 

略微加劇了誤差的增加,因此這種方案仍然不是一種好的方案。

setTimeout 系統(tǒng)時間補(bǔ)償

這個方案是在 stackoverflow 看到的一個方案,我們來看看此方案和原方案的區(qū)別

原方案

setTimeout系統(tǒng)時間補(bǔ)償

當(dāng)每一次定時器執(zhí)行時后,都去獲取系統(tǒng)的時間來進(jìn)行修正,雖然每次運(yùn)行可能會有誤差,但是通過系統(tǒng)時間對每次運(yùn)行的修復(fù),能夠讓后面每一次時間都得到一個補(bǔ)償。

  1. function timer() { 
  2.    var speed = 500, 
  3.    counter = 1,  
  4.    start = new Date().getTime(); 
  5.     
  6.    function instance() 
  7.    { 
  8.     var real = (counter * speed), 
  9.     ideal = (new Date().getTime() - start); 
  10.      
  11.     counter++; 
  12.  
  13.     var diff = (ideal - real); 
  14.     form.diff.value = diff; 
  15.  
  16.     window.setTimeout(function() { instance(); }, (speed - diff)); // 通過系統(tǒng)時間進(jìn)行修復(fù) 
  17.  
  18.    }; 
  19.     
  20.    window.setTimeout(function() { instance(); }, speed); 

再來看看加入額外的代碼邏輯的情況。

依舊非常的穩(wěn)定,因此通過系統(tǒng)的時間補(bǔ)償,能夠讓我們的 setTimeout 變得更加準(zhǔn)時,至此我們完成了如何讓 setTimeout 準(zhǔn)時的探索。

好了我們最后來總結(jié)一下4種方案的優(yōu)缺點(diǎn)

while Web Worker requestAnimationFrame setTimeout 系統(tǒng)時間補(bǔ)償

  while Web Worker requestAnimationFrame setTimeout 系統(tǒng)時間補(bǔ)償
準(zhǔn)確度
主線程阻塞 阻塞 一般 不阻塞 不阻塞
評分 ⭐️⭐️ ⭐️⭐️⭐️ ⭐️ ⭐️⭐️⭐️⭐️⭐️

我們下期再見~

參考

https://segmentfault.com/q/1010000013909430

 

https://stackoverflow.com/questions/196027/is-there-a-more-accurate-way-to-create-a-javascript-timer-than-settimeout

 

責(zé)任編輯:武曉燕 來源: 秋風(fēng)的筆記
相關(guān)推薦

2021-02-07 21:16:04

字節(jié)跳動面試字符串

2021-11-08 09:18:01

CAS面試場景

2021-12-25 22:31:10

MarkWord面試synchronize

2021-01-21 07:53:29

面試官Promis打印e

2024-09-11 22:51:19

線程通訊Object

2023-11-20 10:09:59

2021-12-16 18:38:13

面試Synchronize

2024-02-20 14:10:55

系統(tǒng)緩存冗余

2015-08-13 10:29:12

面試面試官

2025-03-10 03:00:00

CSSline字體

2024-12-25 15:44:15

2024-02-04 10:08:34

2021-12-02 18:20:25

算法垃圾回收

2024-10-22 16:39:07

2024-01-19 14:03:59

Redis緩存系統(tǒng)Spring

2024-01-26 13:16:00

RabbitMQ延遲隊列docker

2024-04-09 10:40:04

2020-07-28 00:58:20

IP地址子網(wǎng)TCP

2010-08-23 15:06:52

發(fā)問

2021-01-06 05:36:25

拉鏈表數(shù)倉數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號