You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionflushBatcherQueue(){runBatcherQueue(queue)runBatcherQueue(userQueue)// user watchers triggered more watchers,// keep flushing until it depletes// userQueue在执行时可能又会往指令queue里加入新任务(用户可能又更改了数据使得dom需要更新)if(queue.length){returnflushBatcherQueue()}// 重设batcher状态,手动重置has,队列等等resetBatcherState()}
Watcher.prototype.run=function(){if(this.active){varvalue=this.get()// 如果两次数据不相同,则不仅要执行上面的 求值、订阅依赖 ,还要执行下面的 指令update、更新dom// 如果是相同的,那么则要考虑是否为Deep watchers and watchers on Object/Arrays// 因为虽然对象引用相同,但是可能内层属性有变动,// 但是又存在一种特殊情况,如果是对象引用相同,但为浅层更新(this.shallow为true),// 则一定不可能是内层属性变动的这种情况(因为他们只是_digest引起的watcher"无辜"update),所以不用执行后续操作if(value!==this.value||// Deep watchers and watchers on Object/Arrays should fire even// when the value is the same, because the value may// have mutated; but only do so if this is a// non-shallow update (caused by a vm digest).((isObject(value)||this.deep)&&!this.shallow)){// set new valuevaroldValue=this.valuethis.value=value}else{// this.cb就是watcher构造过程中传入的那个参数,其基本就是指令的update方法this.cb.call(this.vm,value,oldValue)}}this.queued=this.shallow=false}}
依赖变动后的dom更新
数据变动时触发的notify遍历了所有的watcher,执行器update方法。(删节了shallow update的内容,想了解请看注释)
我们先忽略lazy和同步模式,真正执行的就是将这个被notify的watcher加入到队列里:
pushWatcher把watcher放入队列里之后,又把负责清空队列的flushBatcherQueue放到本轮事件循环结束后执行,nextTick就是vm.$nextTick,利用了MutationObserver,注释里讲述了原理,这里跳过:
runBatcherQueue就是对传入的watcher队列进行遍历,对每个watcher执行其run方法。
可以看到run其实就是先执行了一次this.get(),求出了表达式的最新值,并订阅了可能出现的新依赖,然后执行了this.cb。this.cb是watcher构造函数中传入的第三个形参。
我们回忆一下指令的_bind函数中在用watcher构造函数创造新的watcher的时候传入的参数:
很简单了,其实就是加入了_locked判断后的指令的update方法(一般指令都是未锁住的)。而我们之前就已经举例讲述过指令的update方法。他完成的就是dom更新的具体操作。
好了,其实批处理就是个很好理解的东西,我把收到notify的watcher存放到一个数组里,在本轮事件循环结束后遍历数组,取出来一个个执行run方法,也即求出新值,订阅新依赖,然后执行对应指令的update的方法,将新值更新作用到dom里。
最后
我已经介绍完了Vue的大体流程,Vue为所有需要绑定到数据的指令都建立了一个watcher,watcher跟指令一一对应,watcher最终又精确的依赖到数据上,即使是数组内嵌对象这样的复杂情况。所以在小量数据更新时,可以做到极其精确、微量的dom更新。
但是这种方式也有其弊端,在大量数组渲染时,一方面需要遍历数据defineReactive,一方面需要将数组元素转为scope(一个既装载了数组元素的内容,又继承了其父级vm实例的对象),另一方面所有需要响应式订阅的dom也肯定是O(n)规模,因此必须要建立O(n)个watcher,执行每个watcher的依赖订阅和求值过程。
上述3个O(n)步骤决定了Vue在启动阶段的性能开销不小,同时,在大数据量的数组替换情况下,新数组的defineReactive,依赖的退订、重订过程,和watcher的对应dom更新也都是O(n)级别。虽然最重的肯定是dom更新部分,但其实前两者也依然会有一定的性能开销。而基于脏检查的Angular而言,其不会有那么多的watcher产生变动,也不会有上述前两个过程,因此会有一定的性能优势。
为了满足大量数组变动的性能需求,track-by的提出就显得很有必要,最大可能的重用原来的数据和依赖,只执行O(data change)级别的defineReactive、依赖的退订、重订、dom更新,所以合理优化和复用情况,Vue就具有了很高的性能。我们熟悉了源码之后可以从内部层面进行分析,而不是对于各个框架的性能了解停留在他们的宣传层面。
后续应该还有3篇左右的文章用来介绍网上资料较少的内容:
这篇文章非常长(比我本科的毕业论文都长😂),非常感谢你能看完。Vue源码较长,因为作者提供的功能非常多,所以要处理的edge case就很多,而要想深入了解Vue,源码阅读是绕不开的一座大山。源码阅读过程中很多时候不是看不懂js,而是搞不懂作者这么写的目的,我自己模拟多种情况,调试、分析了很多次,消耗较多精力,希望能帮到同样在阅读源码的你。
The text was updated successfully, but these errors were encountered: