js异步之setTimeout与setInterval

2,254 阅读3分钟

基本介绍

  • setTimeout 延时执行

    setTimeout(fn,1000),1000ms后执行fn
  • setInterval 定时器

    setInterval(fn,100),每隔100m执行fn

特别注意

  • setTimeout执行时间可能会大于给定时间。
  • setInterval间隔执行的时间可能会小于给定时间。

setTimeout

  • JavaScript引擎在执行setTimeout(fn, 10)时,一方面继续执行setTimeout(fn, 10)后面的同步代码,同时另一方面开始计时,在10ms之后将fn插入任务队列中。待所有同步代码执行结束后(JavaScript引擎空闲),依次任务队列中的异步代码。所以,setTimeout(fn, 10)并不能准确的在10ms之后执行,而是大于等于10ms。
  • 代码一(正常)
console.time("time")
setTimeout(function(){console.timeEnd("time")},10);

输出:time:10.9619140625ms

  • 代码二(超时)
console.time();
var i = 0;
for(var s =1;s<100000;s++){
	i += s;
};
setTimeout(function(){console.timeEnd()},10);

输出:time:24.440185546875ms

setInterval

  • 与setTimeout()相同的是,如果当前没有同步代码在执行(JavaScript引擎空闲),则定时器对应的方法fn会被立即执行,否则,fn就会被加入到任务队列中。
  • 由于定时器的事件是每隔10ms就触发一次,有可能某一次事件触发的时候,上一次事件的处理方法fn还没有机会得到执行,仍然在等待队列中,这个时候,这个fn事件就被丢弃,继续开始下一次计时。
  • 需要注意的是,由于JavaScript引擎这种单线程异步的执行方式,有可能两次fn的实际执行时间间隔小于设定的时间间隔。比如上一个定时器事件的处理方法触发之后,等待了5ms才获得被执行的机会。而第二个定时器事件的处理方法被触发之后,马上就被执行了。那么这两者之间的时间间隔实际上只有5ms。
var IntervalId= setInterval(function(){console.log(Date.now())},10);
var i = 0;
for(var s =1;s<1000000;s++){
	i += s;
};
setTimeout(function(){clearInterval(IntervalId)},100);

输出:

1525943129602
1525943129607 // 距离上一次只间隔了5ms
1525943129618
1525943129628
1525943129639
1525943129648
1525943129657
1525943129669
1525943129678
1525943129688
1525943129699
  • setInterval()并不适合实现精确的按固定间隔的调度操作。
  • 比如在处理动画帧数时,就不适合用setInterval,而应采用requestAnimationFrame方法。
// 简单粗暴的方法
var FPS = 60;
setInterval(draw, 1000/FPS);
// 当draw方法执行耗时大于1000/60 ms时,就会发生丢帧的现象。

总结

setTimeout()和setInterval()都不能满足精确的时间间隔。假如设定的时间间隔为10ms,则setTimeout(fn, 10)中的fn执行的时间间隔可能大于10ms,而setInterval(fn, 10)中fn执行的时间间隔可能小于10ms。