Vue 的响应式原理见
官网的这张图
Vue 的更新是生成render函数,然后生成虚拟dom,映射到页面上。左侧的部分其实就是我们watcher的回调,右下角的data就是通过我们上面说的Observer来添加getter和setter。watcher通过dependency和data联系在一起,并触发re-render.
在代码层面上,有 3 个核心的相关类:Observer、Dep和Watcher,Observer是Dep和Watcher之间的桥梁,通过Observer可以为被观察对象设置Dep,然后在特定时刻与Dep.target这个Watcher互相建立以来,从而建立联系。
Watcher会在所关注的Dep发生变化时执行传入的回调,要么是更新数据要么是更新 DOM。
Dep和Watcher是通过观察者模式结合的,Dep是被观察对象,Watcher是观察者。二者是多对多的关系,一个Dep可以被多个Watcher观察,一个Watcher也可以观察多个Dep。
Observer
| 1 | constructor (value: any) { | 
上面的arrayMethods是做了特殊的处理数组Array.prototype,更改了实现:
| 1 | const arrayProto = Array.prototype; | 
如何监听数组的变化可以参考这个 issue。大体来说是类似Array.prototype和Object.prototype这样的原生对象上的 method 是不会受到业务代码的影响的,即使继承一个子类来手动覆盖。
observeArray用于观察一个数组,为每一个数组元素收集依赖:
| 1 | observeArray (items: Array<any>) { | 
walk方法用于处理一个 Object,为每一个 key-value 重新定义getter和setter:
| 1 | /** | 
defineReactive在前一篇文章也有所描述。最终达到的效果是在调用new Obersver(value)或者observe(value)时,都是重写属性的getter和setter。 之后如果有其他地方对 value 的属性值感兴趣,在读取的时候就会收集Watcher作为依赖,在更改属性值时就会触发watcher的更新回调。
Dep
这个类比较简单,核心是内部维护一个subs数组,表示所有对此Dep感兴趣的Watcher。
| 1 | export default class Dep { | 
Watcher
先看一下构造函数
| 1 | constructor (vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean) { | 
看到最后一句会调用get,它用于执行传入的expOrFn:
| 1 | /** | 
pushTarget(this)和popTarget()是一对操作,前者用于将Dep.target设置为当前Watcher,后者用于还原Dep.target:
| 1 | const targetStack = []; | 
在Dep.depend方法中会调用Watcher.addDep,它用于将一个Dep放到自己的deps数组,这是这个Watcher实例所有的依赖Dep。
| 1 | /** | 
在什么时候会实例化一个Watcher呢?目前已知的有两处:
- 在 - mountComponent中会第一次调用- new Watcher,最终的目的是用于更新模板- 1 
 2
 3
 4
 5
 6
 7- new Watcher(vm, updateComponent, noop, { 
 before () {
 if (vm._isMounted) {
 callHook(vm, 'beforeUpdate')
 }
 }
 }
- 在 - state.js中的- initComputed也会调用- new Watcher,并会将返回的- watcher实例放入- vm._computedWatchers对象,目的是监听某个值的变化。- 1 - watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions); 
接着看一下在Dep.notify中会调用的Watcher.update方法:
| 1 | /** | 
update主要有两个出口,一个是run另一个是queueWatcher。如果需要立即执行,那么就会调用run反之会将其加入一个queue中。
run先会调用get获取最新的值,然后和缓存的 value 对比,发生改变时执行new Watcher传入的cb回调函数。
最后看一下queueWatcher。
queueWatcher
| 1 | function queueWatcher(watcher: Watcher) { | 
会有一个专门的调度器Scheduler管理queue,在 flush 时会挨个调用watcher的run方法。
| 1 | /** | 
上面的watcher.before()目前只在mountComponent时设置:
| 1 | new Watcher( | 
也就是说每次执行watcher.update之前都会调用一次beforeUpdate钩子。
还有一个有意思的变量MAX_UPDATE_COUNT,在开发环境下如果两个watcher互相触发对方的update就会陷入死循环,利用这个变量来打破循环。例如下面的代码就会陷入无限循环中:
| 1 | watch: { | 
小结
Vue 的响应式原理核心在于 3 个类,关键之处在于利用Object.defineProperty来重写属性值的getter、setter,利用getter来收集依赖,利用setter来触发更新回调,以达到更新 dom 或数据的目的。