响应式系统(一)
在 Vue 2 的官方文档 - 深入响应式原理 中介绍了 Vue 最独特的特性就是 非侵入性 的响应式系统,所有的数据模型都是对象形式。
在 Vue 2 中,每个 Vue 实例都接收一个 对象形式 Options 来初始化,并通过 Object.defineProperty 来对象里面的一些属性的 默认getter/setter 方法全部转化为 特殊的会触发依赖收集的 getter/setter 方法。
1. 变化追踪过程
官方给出的回答是:每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
2. re-render 与渲染 Watcher
上面的 每一个组件实例都对应的 Watcher 实例,通常我们称为 渲染 Watcher,在 $mount() 首次执行挂载时初始化。
渲染 Watcher 实例配置了一个 执行前函数 watcherOptions.before 和一个执行 watcher.run() 时执行的 回调函数 cb。
watcherOptions.before 主要只有一行内容:
if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') }
即在更新前会触发 beforeUpdate 生命周期钩子,执行配置的相关方法。
而 回调函数 cb 一样只有一点内容:
updateComponent = () => { vm._update(vm._render(), hydrating) }
即用来更新 dom 内容(当然开发环境如果开启了性能分析的话,这里还有其他的处理)
这个过程即是上图的 re-render 过程: Watcher ==> Component Render Function
3. getter 与 setter
上面已经说了 wachter 在变化触发时会执行 vm._update() 和 callHook('beforeUpdate'),而 getter/setter 是如何定义和触发更新的呢?
const property = Object.getOwnPropertyDescriptor(obj, key) const getter = property && property.get const setter = property && property.set Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() isArray(value) && dependArray(value) } } return isRef(value) && !shallow ? value.value : value }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val if (!hasChanged(value, newVal)) { return } if (setter) { setter.call(obj, newVal) } else if (getter) { return } else if (!shallow && isRef(value) && !isRef(newVal)) { value.value = newVal return } else { val = newVal } childOb = !shallow && observe(newVal, false, mock) dep.notify() } })
这里首先是 将对象的 默认 get 和 set 方法保存下来,通过 Object.defineProperty 重新定义该对象每个属性的 get 和 set 方法
get:
首先直接通过 默认 get 获取到值,并且如果这个值是个对象或者数组的话,还会进行深层次遍历,最后返回该属性的值。
当然这里为了兼容 v3 语法,新增了一个 isRef 的判断,用来处理 ref 定义的变量;在以前的版本都是直接返回 value 的。
set:
作为重设对象属性值的方法,为了避免多次更新相同属性值,这里会在最前面增加一个 hasChanged(value, newVal) 的判断,如果多次更新的值一样,则不会重复设置和触发更新。
然后便是属性的更新:
- 对于具有默认 setter 的属性(即在初始化时就定义了的对象属性),会直接调用默认 set 方法
- 如果只有默认 getter 方法,说明该属性不能更新,则直接退出
- 以上都不满足,则直接更新属性值为 newVal
最后一样会处理对象子属性,并通过 dep.notify() 来触发依赖该对象的 watcher 的更新。因为组件渲染 watcher 算是默认的watcher,所以如果该属性更新影响到了页面效果,则会触发视图更新。