1

事件循环

事件循环的流程大致如下:

  1. 所有任务都在主线程上执行,形成一个执行栈。
  2. 主线程发现有异步任务,就在“任务队列”之中加入一个任务事件。
  3. 一旦“执行栈”中的所有同步任务执行完毕,系统就会读取“任务队列”(先进先出原则)。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
  4. 主线程不断重复上面的第三步,这样的一个循环称为事件循环。

宏任务 & 微任务

  如果任务队列中有多个异步任务,那么先执行哪个任务呢?于是在异步任务中,也进行了等级划分,分为宏任务(macrotask)和微任务(microtask);不同的API注册的任务会依次进入自身对应的队列中,然后等待事件循环将它们依次压入执行栈中执行。

+ 宏任务包括

  1. script(整体代码)
  2. setTimeout, setInterval, setImmediate,
  3. I/O
  4. UI rendering
  5. requestAnimationFrame()
  • 微任务包括
  1. process.nextTick
  2. Promise
  3. Object.observe(已废弃)
  4. MutationObserver(html5新特性)

Node 事件循环

requestAnimationFrame

在没有 requestAnimationFrame 方法的时候,执行动画,我们可能使用 setTimeoutsetInterval 来触发视觉变化;但是这种做法的问题是:回调函数执行的时间是不固定的,可能刚好就在末尾,或者直接就不执行了,经常会引起丢帧而导致页面卡顿。

归根到底发生上面这个问题的原因在于时机,也就是浏览器要知道何时对回调函数进行响应。setTimeoutsetInterval 是使用定时器来触发回调函数的,而定时器并无法保证能够准确无误的执行,有许多因素会影响它的运行时机,比如说:当有同步代码执行时,会先等同步代码执行完毕,异步队列中没有其他任务,才会轮到自己执行。并且,我们知道每一次重新渲染的最佳时间大约是 16.6 ms,如果定时器的时间间隔过短,就会造成过度渲染,增加开销;过长又会延迟渲染,使动画不流畅。

requestAnimationFrame 方法不同与 setTimeoutsetInterval,它是由系统来决定回调函数的执行时机的,会请求浏览器在下一次重新渲染之前执行回调函数。无论设备的刷新率是多少,requestAnimationFrame 的时间间隔都会紧跟屏幕刷新一次所需要的时间。需要注意的是这个方法虽然能够保证回调函数在每一帧内只渲染一次,但是如果这一帧有太多任务执行,还是会造成卡顿的;因此它只能保证重新渲染的时间间隔最短是屏幕的刷新时间。

requestIdleCallback

一些低优先级的任务可使用 requestIdleCallback 等浏览器不忙的时候来执行,同时因为时间有限,它所执行的任务应该尽量是能够量化,细分的微任务(micro task)。

因为它发生在一帧的最后,此时页面布局已经完成,所以不建议在 requestIdleCallback 里再操作 DOM,这样会导致页面再次重绘。DOM 操作建议在 rAF 中进行。同时,操作 DOM 所需要的耗时是不确定的,因为会导致重新计算布局和视图的绘制,所以这类操作不具备可预测性。

Promise 也不建议在这里面进行,因为 Promise 的回调属性 Event loop 中优先级较高的一种微任务,会在 requestIdleCallback 结束时立即执行,不管此时是否还有富余的时间,这样有很大可能会让一帧超过 16 ms。


坚果面包
21 声望0 粉丝