JS高級定時器

前提知識

  • JavaScript 是運行於單線程的環境中的,全部任務都在主線程上執行,造成一個執行棧(execution context stack)
  • 除了主線程外,還有一個在進程空閒時執行的「代碼隊列」。隨着頁面在其生命週期中的推移,代碼會按照執行順序添加入隊列。另外,只要異步任務有告終果,回調函數的代碼就會被添加到隊列中。例如,當某個按鈕被按下時,它的事件處理程序代碼就會被添加到隊列。在 JavaScript 中沒有任何代碼是馬上執行的,但一旦進程空閒則儘快執行。
  • 定時器對「任務隊列」的工做方式是,當特定時間過去後將代碼插入隊列。指定的時間間隔表示什麼時候將定時器的代碼添加到隊列中,而不是什麼時候實際執行代碼。

重複定時器(setIntervel)的缺點

當使用 setIntervel()時,僅當沒有該定時器的任何其餘代碼實例時,纔將定時器代碼添加到「任務隊列」。
這樣的機制會致使兩個問題:javascript

  1. 某些間隔會被跳過
  2. 多個定時器的代碼執行之間的間隔可能會比預期的小

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
相關文章
相關標籤/搜索