浏览器 Event Loop 初探

215 阅读2分钟

什么是事件循环?

用户代理为了协调事件、用户输入、脚本(script)、渲染、网络等的一种机制。不通的用户代理有不同的循环规则,目前有这样几类:浏览器事件循环机制,Web Worker 事件循环机制,Node 事件循环机制。

浏览器内核(渲染进程)

浏览器包括主进程、第三方插件进程、GPU 进程以及渲染进程(也就是浏览器内核)。

其中浏览器内核进程包含

event loop.png

  1. GUI 渲染进程 (GUI 渲染线程与 JS 引擎线程是互斥的。)
  2. JS 引擎线程
  3. 事件触发线程(用来控制事件循环,当事件满足出触发条件将事件放入 JS 引擎所在的执行队列中。)
  4. 定时触发器线程(定时任务并不是 JS 引擎来计时,而是定时触发线程来计时。)
  5. 异步 http 请求线程

执行栈和事件队列

同步任务都在 JS 引擎线程上执行,形成一个执行栈

栈中的代码可能会需要通知其它线程工作,例如:定时触发器线程、异步 http 请求线程、其它外部 API。事件满足触发条件以后,会将回调函数放入事件队列。 JS 引擎线程在执行完执行栈中的代码时,会去读取事件队列中的事件的回调函数。

宏任务和微任务

主代码块、setTimeout、setInterval、postMessage、MessageChannel 等,都属于宏任务。每一个宏任务会从头到尾执行,不会执行其它。

Promise、Process.nextTick 都属于微任务

事件循环过程

事件循环过程是一个很复杂的过程,首先有一个常规的流程(涉及到宏任务、微任务、渲染、动画等)。但是浏览器进行了很多优化(例如:渲染队列优化,定时器合并等),所以有时候循环顺序是不符合常规流程的,这也是我们在日常开发中所需要格外注意的。

常规流程

event loop-time.png

异常case

  1. 由于 rendering opportunity 概念存在,并不是每一轮event loop都会对应一次浏览器渲染。会根据屏幕刷新率、页面性能等来决定一个渲染频率,通常这个频率是稳定的。如果已经综合判断此轮不需要渲染,那么requestAnimationFrame等也不会执行。
  2. 定时器有时会进行合并,所以也会打乱渲染顺序。导致不是每次宏任务后面都紧跟着一次渲染。

优化建议

  • 不要阻塞事件循环,多考虑异步。

参考文献

  1. HTML 标准对于事件循环的定义
  2. 从多线程来看 Event Loop
  3. EventLoop 和浏览器渲染、帧动画、空闲回调
  4. I'm stuck in the event loop
  5. 为什么 setTimeout 有最小时延 4ms ?
  6. 从 Promise 源码到异步发展