JS倒計時setTimeout為什么會出現誤差


JS倒計時setTimeout為什么會出現誤差

單線程

JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。這與它的用途有關。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。

任務隊列

單線程就意味着,所有任務需要排隊,前一個任務結束,才會執行后一個任務。如果前一個任務耗時很長,后一個任務就不得不一直等着。

於是,所有任務可以分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行后一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務可以執行了,該任務才會進入主線程執行。

具體來說,異步執行的運行機制如下。(同步執行也是如此,因為它可以被視為沒有異步任務的異步執行。)

(1)所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
(2)主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
(3)一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看里面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重復上面的第三步。

下圖就是主線程和任務隊列的示意圖。

image

Event Loop(事件循環)

主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為Event Loop(事件循環)。

為了更好地理解Event Loop,請看下圖(轉引自Philip Roberts的演講《Help, I'm stuck in an event-loop》)。

image

上圖中,主線程運行的時候,產生堆(heap)和棧(stack),棧中的代碼調用各種外部API,它們在"任務隊列"中加入各種事件(click,load,done)。只要棧中的代碼執行完畢,主線程就會去讀取"任務隊列",依次執行那些事件所對應的回調函數。

定時器

除了放置異步任務的事件,"任務隊列"還可以放置定時事件,即指定某些代碼在多少時間之后執行。這叫做"定時器"(timer)功能,也就是定時執行的代碼。

定時器功能主要由setTimeout()和setInterval()這兩個函數來完成,它們的內部運行機制完全一樣,是將指定的代碼移出本輪事件循環,等到下一輪事件循環,再檢查是否到了指定時間。如果到了,就執行對應的代碼;如果不到,就繼續等待。(區別在於前者指定的代碼是一次性執行,后者則為反復執行。)

為什么使用setTimeout實現倒計時,而不是setInterval?

setInterval指定的是“開始執行”之間的間隔,並不考慮每次任務執行本身所消耗的時間。因此實際上,兩次執行之間的間隔會小於指定的時間。比如,setInterval指定每 100ms 執行一次,每次執行需要 5ms,那么第一次執行結束后95毫秒,第二次執行就會開始。如果某次執行耗時特別長,比如需要105毫秒,那么它結束后,下一次執行就會立即開始。
為了確保兩次執行之間有固定的間隔,可以不用setInterval,而是每次執行結束后,使用setTimeout指定下一次執行的具體時間。

var timer = setTimeout(function f() {
  // ...
  timer = setTimeout(f, 1000);
}, 1000);

上面代碼可以確保,下一次執行總是在本次執行結束之后的1000毫秒開始。(當然存在較小誤差,不然就沒這道題了。。)

setTimeout的作用是將代碼推遲到指定時間執行,如果指定時間為0,即setTimeout(f, 0),那么會立刻執行嗎?

答案是不會。因為必須要等到當前腳本的同步任務,全部處理完以后,才會執行setTimeout指定的回調函數f。也就是說,setTimeout(f, 0)會在下一輪事件循環一開始就執行。

setTimeout倒計時為什么會出現誤差?

setTimeout作為異步任務,在實現倒計時功能的時候,除了執行我們功能的實現代碼,還會有主線程對任務隊列的讀取及執行等過程,這些過程也需要耗費一些時間,所以會因為event loop的機制出現些許誤差。

參考文檔:

https://wangdoc.com/javascript/async/timer.html#運行機制

http://www.ruanyifeng.com/blog/2014/10/event-loop.html

https://www.xuanfengge.com/js-realizes-precise-countdown.html

https://github.com/YvetteLau/Step-By-Step/issues/21

https://juejin.im/post/5a6547d0f265da3e283a1df7

https://liyaoli.com/2015-03-20/count-down.html


免責聲明!

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



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