概念引入
这个概念是在学习vue的时候遇到的,vue官方文档对mounted
钩子函数有一段这样的描述:
注意
mounted
不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在mounted
内部使用vm.$nextTick
但是在实际使用中发现,对于异步子组件,vm.$nextTick
不一定百分百有效。
这里就涉及到了宏任务和微任务的问题,理解了宏任务微任务和vm.$nextTick
的实现原理,我们就可以知道原因。
什么是宏任务和微任务
由于Javascript
是单线程的脚本语言,如果所有代码都是同步执行,当遇到耗时操作时,就会使得浏览器进入假死状态,因此异步任务应运而生。
当执行同步任务遇到一个异步任务时,就在event table
(事件表)中注册回调函数,同步任务继续执行。期间异步任务完成时,回调函数会被放入event queue
(事件队列)。
异步任务之间也是有区别的。宿主环境(浏览器、node)提供的方法是宏任务,例如setTimeout, setInterval。语言标准(js引擎)提供的是微任务,例如Promise。
当同步任务执行完成,依次执行微任务队列中的所有微任务。执行完所有微任务后,从宏任务队列中获取新的宏任务执行。这样就完成了一个事件循环。
宏任务和微任务的具体API可以参考以下[2]:
宏任务[2]
# | 浏览器 | Node |
---|---|---|
I/O | ✅ | ✅ |
setTimeout | ✅ | ✅ |
setInterval | ✅ | ✅ |
setImmediate | ❌ | ✅ |
requestAnimationFrame | ✅ | ❌ |
微任务[2]
# | 浏览器 | Node |
---|---|---|
process.nextTick | ❌ | ✅ |
MutationObserver | ✅ | ❌ |
Promise.then catch finally | ✅ | ✅ |
我们可以从一道经典面试题体验一下事件循环,以下代码会输出什么呢?
setTimeout(_ => console.log(4))
new Promise(resolve => {
resolve()
console.log(1)
}).then(_ => {
console.log(3)
})
console.log(2)
在这个例子中整体代码和setTimeout
是宏任务,Promise.then
是微任务。
进入整体代码(宏任务)后开始,按顺序执行输出1 -> 2,整体代码执行完成后,执行微任务,输出3,然后执行下一个宏任务,输出4。
回到引入问题
为什么在实际使用中,对于异步组件vm.$nextTick
操作子组件的DOM不一定百分百有效呢?
nextTick
实际上是一个微任务(不支持微任务的环境将回退到宏任务):
- 对于同步组件,微任务将在组件渲染后执行,所以
nextTick
执行没有问题。 - 对于异步组件,内部也有异步任务,这个时候就取决于任务队列的执行顺序,所以无法保证
nexTick
达到预期效果。
参考
[1] mounted 不会承诺所有的子组件也都一起被挂载,为什么会有这种情况发生??
[2] 微任务、宏任务与Event-Loop
广告时间
飞书是字节跳动旗下办公套件产品,其将即时沟通、在线协作、音视频会议、日历、云盘、工作台等功能进行了深度整合,为用户提供一站式协作体验。目前,飞书服务的客户已经覆盖了科技互联网、信息技术、制造、建筑地产、企业服务、教育、媒体等多个领域。欢迎投递字节跳动飞书团队,有海量前端后端HC~扫描二维码或者点击链接投递,认准飞书团队👍~
【校招】内推码: HZNVPHS,投递链接: job.toutiao.com/s/JaeUCoc
【社招】投递链接: job.toutiao.com/s/JaevUNo