什么是事件循环?
用户代理为了协调事件、用户输入、脚本(script)、渲染、网络等的一种机制。不通的用户代理有不同的循环规则,目前有这样几类:浏览器事件循环机制,Web Worker 事件循环机制,Node 事件循环机制。
浏览器内核(渲染进程)
浏览器包括主进程、第三方插件进程、GPU 进程以及渲染进程(也就是浏览器内核)。
其中浏览器内核进程包含
- GUI 渲染进程 (GUI 渲染线程与 JS 引擎线程是互斥的。)
- JS 引擎线程
- 事件触发线程(用来控制事件循环,当事件满足出触发条件将事件放入 JS 引擎所在的执行队列中。)
- 定时触发器线程(定时任务并不是 JS 引擎来计时,而是定时触发线程来计时。)
- 异步 http 请求线程
执行栈和事件队列
同步任务都在 JS 引擎线程上执行,形成一个执行栈
。
栈中的代码可能会需要通知其它线程工作,例如:定时触发器线程、异步 http 请求线程、其它外部 API。事件满足触发条件以后,会将回调函数放入事件队列
。
JS 引擎线程在执行完执行栈
中的代码时,会去读取事件队列
中的事件的回调函数。
宏任务和微任务
主代码块、setTimeout、setInterval、postMessage、MessageChannel 等,都属于宏任务
。每一个宏任务会从头到尾执行,不会执行其它。
Promise、Process.nextTick 都属于微任务
事件循环过程
事件循环过程是一个很复杂的过程,首先有一个常规的流程(涉及到宏任务、微任务、渲染、动画等)。但是浏览器进行了很多优化(例如:渲染队列优化,定时器合并等),所以有时候循环顺序是不符合常规流程的,这也是我们在日常开发中所需要格外注意的。
常规流程
异常case
- 由于 rendering opportunity 概念存在,并不是每一轮event loop都会对应一次浏览器渲染。会根据屏幕刷新率、页面性能等来决定一个渲染频率,通常这个频率是稳定的。如果已经综合判断此轮不需要渲染,那么requestAnimationFrame等也不会执行。
- 定时器有时会进行合并,所以也会打乱渲染顺序。导致不是每次宏任务后面都紧跟着一次渲染。
优化建议
- 不要阻塞事件循环,多考虑异步。