本篇文章主要是对Vue中的DOM异步更新策略和nextTick机制的解析,需要读者有一定的Vue使用经验并且熟悉掌握JavaScript事件循环模型。
引入:DOM的异步更新
打印的结果是begin, 而不是我们设置的end。这个结果足以说明Vue中DOM的更新并非同步。
在Vue官方文档中是这样说明的:
可能你还没有注意到,Vue异步执行DOM更新。只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作上非常重要。然后,在下一个的事件循环“tick”中,Vue刷新队列并执行实际 (已去重的) 工作。
简而言之,就是在一个事件循环中发生的所有数据改变都会在下一个事件循环的Tick中来触发视图更新,这也是一个“批处理”的过程。(注意下一个事件循环的Tick有可能是在当前的Tick微任务执行阶段执行,也可能是在下一个Tick执行,主要取决于nextTick函数到底是使用Promise/MutationObserver还是setTimeout)
Watcher队列
在Watcher的源码中,我们发现watcher的update其实是异步的。(注:sync属性默认为false,也就是异步)
queueWatcher(this)函数的代码如下:
这段源码有几个需要注意的地方:
1、首先需要知道的是watcher执行update的时候,默认情况下肯定是异步的,它会做以下的两件事:
- 判断has数组中是否有这个watcher的id
- 如果有的话是不需要把watcher加入queue中的,否则不做任何处理。
2、这里面的nextTick(flushSchedulerQueue)中,flushScheduleQueue函数的作用主要是执行视图更新的操作,它会把queue中所有的watcher取出来并执行相应的视图更新。
3、核心其实是nextTick函数了,下面我们具体看一下nextTick到底有什么用。
nextTick
nextTick函数其实做了两件事情,一是生成一个timerFunc,把回调作为microTask或macroTask参与到事件循环中来。二是把回调函数放入一个callbacks队列,等待适当的时机执行。(这个时机和timerFunc不同的实现有关)
首先我们先来看它是怎么生成一个timerFunc把回调作为microTask或macroTask的。