JAVAScript 的事件循环(最新w3c标准)
1.事件循环定义
- 作用:协调事件、用户交互、脚本、渲染、网络等异步操作。
众所周知,JavaScript 是单线程语言,已每秒60帧为例子任务的执行频率为16.6(1000/60),所以JavaScript的事件循环机制就是用来协调这些任务的。这些任务会被存放在不同的队列中,然后按照一定优先级顺序执行(具体需要看浏览器调度)。
- 完整的事件循环流程
A[开始执行代码] --> B[执行同步代码]
B --> C{执行栈是否为空?}
C -->|否| B
C -->|是| D[执行所有微任务]
D --> E{微任务队列是否为空?}
E -->|否| D
E -->|是| F[执行一个消息队列任务]
F --> G[渲染页面]
G --> C
2.任务队列-W3C的最新解释
在以前JavaScript的异步,最常见的解释是说宏任务和微任务。但是随着浏览器需要应对日益复杂的页面效果需求,单纯的把任务队列分类成俩个已经不适合新的w3c标准了。
- 每个任务都有一个任务类型,同一个类型的任务必须在同一个队列,不同类型的任务可以属于不同队列。
- 浏览器必须有一个微队列,微任务队列中的任务优先于所有其他队列中的任务。
任务没有优先级,但是任务队列有优先级,优先级高的队列中的任务会优先执行。微任务的执行顺序永远是优先的,其他的队列的优先级不会永远不变,这涉及到浏览器的调度问题(举个例子:一家餐馆有俩条队伍,一条普通一条vip,vip队伍的优先级高,但是在当vip队伍有几百上千个的时候,餐点老板总不能让普通队伍的客户一直等着)。
消息队列是一个合集,在目前的 chrome 的实现中,至少包含了下面的队列:
- 延时队列:用于存放计时器到达后产生的回调任务,优先级中
- 交互队列:用于存放用户操作后产生的事件处理,优先级高
- 微队列:用于存放需要最快执行的任务,优先级最高
3.微任务
微任务的触发时机在每一个任务执行之后。 常见的微任务有:
- process.nextTick
process.nextTick 是 Node.js 中用于将回调函数推迟到下一个事件循环顶部执行的方法,具备最高优先级。
- MutationObserver
MutationObserver 是浏览器提供的 API,用于监听 DOM 树的变化(如节点增删、属性修改)。
- Promise.then catch finally
Promise 是处理异步操作的标准方案,通过链式调用管理成功(then)、失败(catch)和最终逻辑(finally)。
//new Promise 是一个同步任务 then catch finally才是异步任务
new Promise(resolve => {
resolve()
console.log(1)
}).then(res => {
console.log(3)
})
console.log(2)
//打印: 123
4.视图更新渲染
一次事件循环结束后,浏览器会执行视图渲染,当然这里会有浏览器的优化,可能会合并多次循环的结果做一次视图重绘(人话:卡了,掉帧了),因此视图更新是在事件循环之后,所以并不是每一次操作 Dom 都一定会立马刷新视图。视图重绘之前会先执行 requestAnimationFrame 回调,那么对于 requestAnimationFrame 是微任务还是宏任务是有争议的,在这里看来,它应该既不属于微任务,也不属于宏任务。
5.面试题:阐述一下JS的事件循环
事件循环又叫做消息循环,是浏览器渲染主线程的工作方式 在chrome中,chrome会开启一个 不会结束的for循环,每次循环从消息队列中取出第一个任务进行执行,而其他线程只需要在合适的时候将任务加入到对应的队列的末尾即可。 过去把消息队列分为宏任务队列和微任务队列,目前已无法满足浏览器复杂的环境 根据W3C最新解释,每个任务有自己的任务类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。不同的任务队列有不同的优先级,在一次事件循环中,由浏览器决定取哪一个队列的任务,但是浏览器必须有一个微队列,微队列具有最高的优先级
来源是渡一