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渲染出来)

猜你喜欢

转载自blog.csdn.net/a380776767/article/details/80050332