DaraW

Code is Poetry

Vue响应式原理之Watcher

最近在看Vue的响应式原理时明白了Vue的一些具体的优化手段,在一个数据被操作2次的时候DOM并不会一定跟着修改2次,正常情况下只有一次的修改。
首先Vue响应式原理的大概内容是借助gettersetter来劫持数据的变动的,之前我也曾经写过一篇文章来分析数据绑定的几种实现(如何监听JS变量的变化),但是最近稍微深入了解了Vue的实现原理后发现Vue其实是要比想象中实现的巧妙多了。

在阅读Vue 源码解析:深入响应式原理的时候,明白了Vue在getter中收集变量的依赖,在setter中通知变量的变化。Vue的内部并不是简单粗暴的把监测钩子插在settergetter中,而是巧妙的使用了观察者模式(Pub/Sub),Dep就是其实现。而模版中的指令和数值检测的绑定则通过Watcher来完成,每个被监测的变量会被分配一个Watcher,每个Watcher都有一个id来做去重。
在被检测的数值发生变化时,setter中通过dep.notify来发布通知,通知所有的Watcher实例调用update方法,而在大多数情况下,Watcher实例调用update又会把自身放入一个队列中,在下一次事件循环中执行flushSchedulerQueue来一起更新。这一块是用自定义的nextTick函数来做的,这个函数的实现已经有很多的文章进行的讲解,知乎上也有过讨论,这里不再赘述。
这个时候我产生了一个很智障的想法,既然每个被监测的变量对应了一个Watcher实例,那么假设vm上有一个值x,初始值为1vm.x++连续同步执行两次,第一次执行的时候其Watcher实例被推入了更新队列中,第二次的时候在queueWatcher中是有一个has对象当作Map来使用,判断是否已经存在id对应的对象来决定要不要推入队列的,vm.xWatcher已经被推入过一次,是不会再次被推入的,那么第二次变动是不是会被直接忽略?直觉告诉我Vue肯定不会犯这种低级错误啊!然而这块的代码其实还是有点绕的,虽然读懂了大概的流程,但是有的细节处理不是一时半会儿就能直接理解的。
晚上刷知乎的时候看到了一个高赞回答,讲的是如何使用Chrome的断点调试功能查看Reactthis.setState背后发生了什么,我突然想到既然自己看代码不能理解,那么打断点跑一下不就好了!
在Chrome中打断点跑了一遍后我突然想明白过来了,事实上vm上的变量和DOM上的展示并不是完全实时映射的啊,在vm.x++第一次执行后vm.x就已经被认为是脏的了,下一次清空watcher队列的时候他肯定是会进行处理的的,而这个时候,vm.x的值已经是2了;在执行完第二次的自增操作后,vm.x的值变成了3,这时候如果主线程中没有其他的操作(这一轮EventLoopMacrotask队列已经清空了),就会去清空Watcher实例队列,而这时候DOM才会更新到vm.x的值所对应的样子。

后话

上面的解释是基于Vue2.2.1来分析的,的这里再做一个大胆的猜测,不过我还没有去看源码来验证,Vue2在加入虚拟DOM后,除了能够让Weex和SSR这些多端渲染的实现变得更加容易外,其实也是能够有一定的优化的,假设vm.x++后紧跟一个vm.x--x虽然是脏的了,但是x并没有发生什么变动,那么可以在V DOM在Diff的时候会把x对应的依赖的DOM修改忽略掉,兼具了依赖收集和虚拟DOM的优点,当然也会付出依赖收集和虚拟DOM的内存占用和计算的成本。

参考资料&推荐阅读

Proudly powered by Hexo and Theme by Hacker
© 2018 DaraW