那么Vue中data数据改变后会触发几次render呢
粗略看过vue2
的部分源码之后,尝试自己写个简单版出来。结果发现自己实现的版本里边一次data
修改触发不止一次render
。
这里说的render指的是生成新的虚拟dom, 并与先前的虚拟dom进行diff操作,将得到的差异部分做增量的dom更新。
这是之前写的部分代码,嗯完整的点这里
1 | // 实例化一个Watcher对象,收集data和computedData相关的所有数据依赖 |
显然如果是在vue中应该只触发一次,不然就是一个待优化项,来看看vue
是怎么做的。
188 | } else { |
这里称这个new Watcher
产生的实例为renderWatcher
在updateComponent
第一次执行完成了组件的渲染和挂载, 然后也顺便完成了对组件的renderWatcher
的依赖收集,此后如果数据发生变化,则触发renderWatcher
再次通过自己的getter
方法(即updateComponent
)来获取新的结果值。 于是达到了更新视图的目的。
Watcher
实例化时接收的第二个参数为getter
方法, 一般情况下会在实例化的同时执行一次getter
来获取目标值,并且收集之前由observe
方法“种”下的依赖。当之后依赖的值发生变化,则触发Watcher
实例重新获取目标值。
于是改完之后代码变成了这样
1 | // 重绘触发 |
新的问题
实际上如果只是这样的话还是差点意思
举个例子, 给一个按钮绑定一个点击事件
1 | // ... |
新的问题是:
点击按钮之后会触发几次render呢?
在vue中的话,那自然是触发一次render了。
需要说明的是,vue2是通过设置对象属性的getter来触发数据的依赖收集,通过设置对象属性的setter来触发数据的变化通知。
这里说setter被调用时触发的变化通知,它通知的是依赖当前被修改数据的其他数据(比如computed属性),或者是通知组件重新生成虚拟dom然后diff然后重新render。
在方法onBtnClick
中有两项数据修改,那么就会触发两次变化,但是实际上只会触发一次render。
为了达成这个结果, vue2中是这么做的
根据上文分析的部分源码,已经知道方法onBtnClick
执行后, 属性btnClickCount
和otherData
都发生了变化, 于是触发了两次renderWatcher
实例的更新。从代码执行层面,则是会调用两次renderWatcher.update
。
这里来看Watcher.update
的源码
160 | /** |
结合上文中renderWatcher
实例化过程中的传参以及Watcher
的构造函数,可以确定renderWatcher
的lazy
和sync
属性均为false
;
于是可知queueWatcher(renderWatcher)
被执行了两次。
然后接着去找queueWatcher
的实现
125 | /** |
明显可以这里做了去重处理(watcherId全局唯一),看出如果连续两次执行queueWatcher(renderWatcher)
,起作用的只有第一次。
这里只是将renderWatcher
排到了将来要处理的队列里边。
具体处理的时机则是nextTick
之后。
而vue2的nextTick
中安排的flushSchedulerQueue
则会在onBtnClick
方法执行,所有相关的watcher实例被放进队列之后再开始执行。
顺便一提,flushSchedulerQueue中处理每个watcher之前会将当前队列中的所有watcher按照id排个序,id最大的排在最后。
而这个id最大的Watcher实例, 就是renderWatcher, 因为它是最后一个实例化的Watcher,于是在computed等其他属性更新完毕之后,最后调用一次updateComponent, 即对应文章开头所说的render。