[JavaScript] 關於setTimeout()、setInterval()以及requestAnimationFrame()的使用方法

JavaScript
setTimeout()
setInterval()
requestAnimationFrame()

參考權威:MDN

setTimeout()與setInterval()

由瀏覽器提供的WebAPI,我們將請求丟到瀏覽器來做出非同步請求的感覺


先談談setTimeout()

setTimeout( callback_func, milliseconds, parameter1, parameter2, ...)


x毫秒後執行callback function,callback_func必帶,其他參數則是optional,毫秒沒寫的話預設值為0


後面那些parameters是給callback_func的參數,範例如下,注意global a 和local a的不同


output第一個"Usada"是第四行的結果

var a = "Usada"
setTimeout((a)=>{var a = a + " Pekora"; console.log(a)})  //undefined Pekora
setTimeout((a)=>{var a = a + " Pekora"; console.log(a)},0,"Usada")  //Usada Pekora
console.log(a)  //Usada
setTimeout(()=>console.log(a),2000)   //Usada

//output
Usada
undefined Pekora
Usada Pekora
Usada


setTimeout的請求會被丟到WebAPI端,丟出去時便從stack暫時移除了


這也是為啥第四行"console.log(a)"會先執行的緣故


那麼到底WebAPI的timer怎麼計時呢?

setTimeout(()=>console.log("fin"),10000)
for (let x = 1;x<9999999999;x++){
  var y = y + Math.sqrt(x)
}

上面單獨執行for迴圈的話,我的電腦大概需要40.639s


連同setTimeout一起執行,則需約40.63s


這說明了

setTimeout被丟入WebAPI時便開始計時
在WebAPI timer數完後,會將callback function丟入task queue
直到stack內該執行的都執行完畢後(when the stack is empty),task queue才依先後順序丟入stack執行


因此才會說setTimeout的計時不是真的時間


以上面例子來看,當計算量很大且你又把setTimeout寫在計算式前的時候


結果上來說看不到setTimeout想要停留的那10秒


想撐十秒,得把setTimeout移到for迴圈之後~


setTimeout本身是什麼?

也許這樣描述有點怪...請看常見的code

function sayHi(who) {
  alert(`Hello ${who}!`);
}
let hiPeko = setTimeout(sayHi, 2000, 'Pekora');

通常會指定setTimeout給一個var或let,如果你console.log該變數,會得到一個整數值

表示'timerID'

存成變數的用意在於要清除timer時才找的到人

clearTimeout( timer_ID )


談談setInterval()

setIInterval( callback_func, milliseconds, parameter1, parameter2, ...)


基本觀念同setTimeout(),差別在於setInterval()是每隔固定毫秒就會執行一次callback function


一樣常常用var/let做成變數方便後續清除找人,要清除的話使用clearInterval(timer_ID)


就這樣~講完啦!


進階使用細節

setInterval()所設定的時間間隔,其實包含了其callback_func執行的運算時間


舉例來說:設定時間間隔1000ms,callback_func運算將耗掉400ms,那麼剩下的interval時間僅有600ms


也就是說400ms運算完console.log出結果,到進入下一個interval開始執行運算,只剩600ms


setTimeout()則是保證每次callback_func執行間隔值為固定你給的那個毫秒值


所以寫成遞迴方式實作setInterval效果時,能確保每個cbf執行間隔一致

//每隔1秒印出"hi Pekora!"
setTimeout(function hiPeko() {
 console.log("hi Pekora!");
 setTimeout(hiPeko, 1000);
}, 1000);


requestAnimationFrame()介紹

用於瀏覽器rerender時,專門run動畫的setInterval()現代版


creativejs一篇詳細說明的文章


簡言之,requestAnimationFrame()用於解決setTimeout和setInterval在渲染動畫時無法有效使用CPU資源的問題


MDN提供的語法範例如下

function draw() {
  // Drawing code goes here
  requestAnimationFrame(draw);
}

draw();

注意到了嗎?


沒錯!沒有time interval rate參數!


因為requestAnimationFrame()的動畫渲染時間間隔取決於使用者的瀏覽器本身的刷新速率(通常為60fps)


且並非固定一直維持這個刷新速率,當瀏覽器有需要時才是!


所以你切到其他分頁時,舊的頁面內的動畫不會一直狂刷新耗資源


使用requestAnimationFrame()的好處還包含能統一你做的網頁的動畫刷新(save your CPU~)


須留意老舊瀏覽器的支援性就是了...



就是需要給定fps怎辦?

實務上需要強制給定requestAnimationFrame()刷新頻率的話


還記得前面說setTimeout保證執行間隔的這件事嗎?


對的~


用setTimeout + requestAnimationFrame的邏輯來實作(以fps = 15舉例)

var fps = 15;
function draw() {
  setTimeout(function() {
    requestAnimationFrame(draw);
    // Drawing code goes here
  }, 1000 / fps);
}



固定時間間隔刷新動畫

除了fps外,想寫成固定時間間隔刷新也可以


code example from joelambert

(好code,看不懂的話就再看十次Ovo)

window.requestInterval = function (fn, delay) {
  //確保瀏覽器支援性
  if (
    !window.requestAnimationFrame &&
    !window.webkitRequestAnimationFrame &&
    !(window.mozRequestAnimationFrame && window.mozCancelRequestAnimationFrame) && // Firefox 5 ships without cancel support
    !window.oRequestAnimationFrame &&
    !window.msRequestAnimationFrame
  )
    return window.setInterval(fn, delay);


  var start = new Date().getTime(),
    handle = new Object();


  function loop() {
    var current = new Date().getTime(),
      delta = current - start;


    if (delta >= delay) {
      fn.call();
      start = new Date().getTime();
    }


    handle.value = requestAnimFrame(loop);
  }


  handle.value = requestAnimFrame(loop);
  return handle;
};


© 2021 Hamsterism. All rights reserved github