Skip to content

Latest commit

 

History

History
69 lines (42 loc) · 3.88 KB

16.setTimeout是如何实现的.md

File metadata and controls

69 lines (42 loc) · 3.88 KB

WebAPI:setTimeout是如何实现的?

  • 消息队列 && 事件循环应用

  • 定时器setTimeout,制定某函数在多少秒之后执行,会返回一个整数来表示定时器的编号,也可以通过该编号取消这个定时器(clearTimeout(timerId))

const setTimeout = () => {
	setTimeout(() => {
		console.log('3秒后才会打印')
		}, 3000)
}
setTimeout();
//  return a timerId, can cancel this timer using TimerId

浏览器怎么实现setTimeout

  • 先回顾事件循环: 渲染进程所有执行在主线程上的任务需要先添加到消息队列,然后事件循环系统再按照顺序执行消息队列中的任务

  • 执行一段异步代码,需要先将任务添加到消息队列里,通过定时器设置的回调函数在制定事件内进行调用,但消息队列中的任务是按照顺序执行的,所以为了保证回调能在制定时间内执行,不能将定时器的回调函数直接添加到消息队列里面

  • Chrome除了正常使用的一个消息队列,还有另外一个消息队列(延迟队列),其维护了需要延迟执行的任务列表,包括了定时器和一些Chromium内部需要延迟执行的任务, 所以JS创建一个定时器时, 渲染进程会将该定时器的回调任务添加到延迟队列中

  • 定义如下:

 DelayedIncomingQueue delayed_incoming_queue;
  • JS调用setTimeout设置回调函数时,渲染进程会创建一个回调任务,包含回调函数回调发起时间延迟执行时间

  • 执行流程: 执行消息队列任务,事件循环执行状态保持为true,然后执行延迟队列中的任务,如果执行状态不为true,则直接退出线程循环。

  • 浏览器内部实现取消定时器操作也比较简单,直接从延迟队列中通过ID查找对应的任务,然后任务出队删除就可以了

使用setTimeout注意事项

  • 当前任务执行时间过久, 会影响延迟到期定时器任务执行

  • 如果setTimeout存在嵌套调用,那么系统会设置最短时间间隔为4毫秒:

function cb () {setTimeout(cb, 0); }
setTimeout(cb, 0)
  • 一些实时性较高的需求就不太适合用setTimeout了,定时器实现JS动画(坚决不行,可考虑使用requestAnimationCallback && requestAnimationFrame)

  • 未被激活的页面,setTimeout执行最小间隔是1000毫秒(非激活标签为了优化后台页面的加载损耗和降低耗电量设置)

  • 延迟执行时间有最大值(设置的延迟值大于 2147483647 毫秒(大约 24.8 天)时就会溢出,这导致定时器会被立即执行)

  • 使用setTimeout设置的回调若是某个对象的方法,this不符合直觉(执行上下文),不建议使用,因为会指向全局的this而不是定义所在的那个对象,如果要解决,可以将对象中的方法放到匿名函数中执行,或者显示使用绑定将其绑定在对象上面(bind)

总结

  • 支持定时器实现, 增加延时队列
  • 消息队列排队执行任务,所以导致延时队列中的任务有时来不及按照延时时间来执行,如果实时性比较高的需求不建议使用

requestAnimationFrame 的工作机制

  • 使用 requestAnimationFrame 不需要设置具体的时间,由系统来决定回调函数的执行时间,requestAnimationFrame 里面的回调函数是在页面刷新之前执行,它跟着屏幕的刷新频率走,保证每个刷新间隔只执行一次,如果页面未激活的话,requestAnimationFrame 也会停止渲染,这样既可以保证页面的流畅性,又能节省主线程执行函数的开销

  • 用法: window.requestAnimationFrame(callback)

  • 通常是一秒执行六十次(屏幕刷新时间 16ms大约一次)

  • 对比优点: 系统决定执行时间,在主线程中执行时,不受到任务执行时间影响, 即时setTimeout设置16ms执行一次同样也会受其他任务执行时间影响,有时候也不会按时执行