NextTick原理分析
面试题
- nextTick的原理和作用
- nextTick具体怎么实现的呢
异步渲染视图
为什么数据修改之后,没有立刻渲染视图?
在Vue.js中,当状态发生变化时,watcher会得到通知,触发虚拟DOM的渲染流程,watcher触发渲染这个操作是异步的。更新DOM是通过使用vm.$nextTick
注册到微任务中。
异步的原因:为了减少性能消耗
数据变化后,组件内的所有状态变化都会通知到同一个watcher,然后对整个组件进行Diff并更改DOM。假设在同一轮事件循环中有多个数据发生了变化,那么组件的watcher会收到多份通知,进行多次渲染。
为了减少性能消耗,我们可以等所有状态修改完毕后,一次性将整个组件的DOM渲染到最新。所以将收到通知的watcher实例添加到队列中缓存起来,在添加队列之前检查其中是否已经存在相同的watcher,在下一次事件循环中,在触发渲染流程
事件循环机制采用的是浏览器事件循环机制
nextTick的作用
nextTick的作用:将回调延迟到下次DOM更新之后执行
本质:将回调添加到任务队列中延迟执行
-
下一次DOM更新是指下次微任务执行时更新DOM的操作,默认nextTick添加到
microtasks
中,,但在特殊情况下会使用macrotasks
,比如v-on
。 -
更新DOM的回调和
vm.$nextTick
注册的回调,都是添加到微队列中,nextTick需要用在DOM更新循环结束后。所以需要注意顺序问题,如果需要在vm.$nextTick
中获取更新后的DOM,先修改数据,再使用vm.$nextTick
注册回调
nextTick方法的原理
默认是添加到微队列中:使用Promise.then
假设添加到宏队列中: 使用setImmediate
如果有兼容性问题,会使用其他方案替换
添加到微队列中
- 使用一个列表
callbacks
来存储vm.$nextTick
参数中的回调,在一轮事件循环中,vm.$nextTick
只会向队列添加一个任务。
如果没有指定回调,vm.$nextTick函数返回一个成功Promise。具体实现步骤是在callbacks中添加一个函数,当这个函数被执行时,执行Promise的resolve
- 使用Promise.then将任务添加到微队列中,当浏览器不支持Promise时,会走加入宏队列的流程。使用一个变量pending来标记是否已经向任务队列中添加了一个任务,添加前先判断任务队列中是否存在任务。
let microTimerFunc;//作用:使用Promise.then将任务添加到微队列中
const p = Promise.resolve();//返回一个成功的promise
microTimerFunc = ()=>{
p.then(flushCallbacks);//flushCallbacks函数的代表一个任务,会将列表中的回调依次取出执行
}
- 多次调用
vm.$nextTick
时,如果已经添加进了任务,会将回调添加到回调列表中缓存。 - 当任务触发时,依次执行列表中的所有回调并清空列表,更改pending变量的状态。
添加到宏队列中
增加了函数withMacroTask,该函数包裹的函数使用的所有vm.nextTick
方法都会将回调添加到宏队列中,包括状态被修改后触发的更新DOM的回调和用户自己使用vm.nextTick
注册的回调等
优先采用setImmediate
方法
macroTimerFunc = ()=>{
setImmediate(flushCallbacks);//flushCallbacks函数的代表一个任务,会将列表中的回调依次取出执行
}