Vue的nextTick原理解析

假设当前的模板代码为<div id="a">{{a}}</div>,这时我们在mounted钩子里写下如下的代码:

this.a = '纳尼?!';
this.$nextTick(function(){
	console.log($('#a')[0].textContent);
})

于是vue内部的执行流程如下图所示:

这里写图片描述

首先外部代码(即mounted钩子中的代码)是一整个task任务,执行完后才会执行microtask任务队列。
分析第一行代码:this.a = '纳尼?!';
前面的文章提到vue的数据绑定是通过修改get/setter来实现的,于是this.a = '纳尼?!';被执行时,a属性的setter会被触发,从而通知所有订阅了a属性的watcher。watcher收到通知后,如果该watcher未存在于更新数组中则将其推入更新数组(保证了单次task任务只会修改dom一次)。然后将flushBatcherQueue放入nextTick的cb数组中(flushBatcherQueue的作用是遍历watcher更新数组,执行watcher们的更新方法)。接着修改MO监听的textNode,这样会触发MO将其回调函数加入到microtask队列中。
分析第二行代码:this.$nextTick(function(){ console.log($('#a')[0].textContent); })
这句代码会把console这个函数添加到nextTick的cb数组中,然后task执行完毕,开始执行microtask队列。这时就会执行到MO的回调函数,而MO的回调函数会执行nextTick的cb数组,即先遍历执行wathcer,再执行我们定义的console回调。
总结一下:在一次task代码中,数据可能被多次修改。而我们不能在每次修改时都立马通知watcher去更新dom,替代的做法是将watcher加入到更新数组中。等到task代码执行完毕后(即所有同步代码执行完毕),则代表这一轮的数据修改已经结束。这时候我们可以去触发watcher的更新操作,于是无论之前task代码修改了多少次,最终我们只会更新DOM一次。

PS:nextTick的重点在于将flushBatcherQueue这步遍历watcher的操作放在microtask中执行,至于使用MO或者Promise.then都无所谓。在这两者都不能很好兼容的环境下会被迫使用setTimeout来代替。但setTimeout是将回调函数放在macrotask队列,而浏览器在清理完microtask队列时会触发ui rendering,这样setTimeout就会浪费了它之前的浏览器ui rendering机会。(即至少要两次ui rendering才能把更新后的DOM渲染出来)

写在最后

我个人开了一个公众号“前端搬运小工”,我会定期推送优秀的前端精选文章,拒绝无脑基础入门的文章,带给你不一样的前端视角。
在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值