继续初始化中未完的内容
初始化流程中,还未提到 initState,这个方法涉及 vue 中有关数据状态的一些操作:
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) }}
initState 方法内涉及 Vue 主要几个 options 属性的初始化,他们具备数据动态响应特性,我们大致看下他们的方法的内部结构:
initProps
function initProps (vm: Component, propsOptions: Object) { //... defineReactive(props, key, value)}
initData
function initData (vm: Component) { //... observe(data, true /* asRootData */)}
initComputed
function initComputed (vm: Component, computed: Object) { //... watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions )}
initWatch
function initWatch (vm: Component, watch: Object) { //... createWatcher(vm, key, handler[i])}
注意,没有贴出 initMethods 方法,因为如果它和 props 重名,会覆盖掉 props 的定义。所以它不具备数据响应功能。只是因为流程关系,放在了 props 之后。
下面开始 Vue 里的数据响应说明。
从 initData 开始,observe 观察对象
因为 data 是我们用的最多的属性,同时所以的数据都在此定义,initData 也是 vue 初始化状态中最代表性的方法,懂了它,其他的初始化方法也会很快理解。
先是做一系列和 props、methods 的校验 warn 判断,最后执行 observe 方法:
function initData (vm: Component) { //... observe(data, true /* asRootData */)}
observe 翻译为:观察。此处它的入参是 data,所以会观察 data 对象。也是整个数据响应的入口。
所以抱着这个思路来看后续的代码:
export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } //... return ob}
此方法将获得 ob 对象,此对象将从 Observer 观察者中创建获取 or 从当前 value 中的 _ ob_ 属性中获取。
注意,只有数组,对象等复杂类型数据才会被创建观察者对象。要是像普通的 Number、String 将不会被观察。
观察者 Observer
下面是 Observer 类方法的代码:
export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { this.walk(value) } } /** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } /** * Observe a list of Array items. */ observeArray (items: Array) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } }}
所有的内容将围绕构造器 constructor 开始。
通过 def 方法,定义 _ob_ 属性
构造器中会通过 def 方法来定义 _ob_ 属性:
constructor (value: any) { def(value, '__ob__', this)}
def 方法中,将见到 vue 所用"活"的 defineProperty 方法:
export function def (obj: Object, key: string, val: any, enumerable?: boolean) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true })}
通过 defineProperty 原生方法,往当前 value 对象(data)新添加 _ ob_ 属性,并且设置相关的对象属性描述特征。
解释下 def 的传入参数:obj 是我们 vm.$options 中设置的 data 对象,key 为 _ ob_ 属性,val 为 Observer 的 this 引用(属性包括:value,dep,vmCount):
通过 walk 方法,遍历 data 属性
之后,构造器最后通过 walk 方法,将 vm.$options.data 上所有的对象属性统统执行 defineReactive 方法:
constructor (value: any) { //... this.walk(value)}
walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) }}
通过 defineReactive 对 data 属性赋予动态响应能力
因为涉及 Dep 模块,先大概了解此方法的简化结构:
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) { const dep = new Dep() //... Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { //... }, set: function reactiveSetter (newVal) { //... } })}
传入 defineReactive 的 obj 对象会是这个样子(这也是 def 方法执行后的结果):
接下来看 defineReactive 内部的逻辑。
创建 Dep 对象
在这里会创建一个新的 Dep 依赖订阅对象:
const dep = new Dep()
初始化已定义的 getter/setter
获取 data 对象上属性的具体描述说明,并且取得 getter/setter 访问器属性:
const property = Object.getOwnPropertyDescriptor(obj, key)const getter = property && property.getconst setter = property && property.set
注意,会根据 arguments 参数预先执行 setter 方法:
if ((!getter || setter) && arguments.length === 2) { val = obj[key]}
然后会重新对 data 对象上的属性来定义 getter/setter 访问器属性:
let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { //... }, set: function reactiveSetter (newVal) { //... } })
reactiveGetter
先看 getter 的定义:
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() if (Array.isArray(value)) { dependArray(value) } } } return value } //...}
当模板解析后,就会调用其中设置的变量(如下,message),从而触发 get 方法:
{{ message }}
这里会判断 Dep.target ,是否有监听对象 Watcher。
if (Dep.target) { //...}
具体哪里对 Dep.target 进行了赋值,见下篇中的:依赖订阅器 Dep > 哪里设置 Dep target
我们先假设 Dep.target 存在,那么将执行 dep.depend 方法;如果子属性可以被观察,也将进行依赖解析:
if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() //.. }}
到 Dep 模块具体再看 depend 的作用。
reactiveSetter
setter 相对简单,主要流程如下:
- 当对 data 中的属性更新时,先触发上面的 getter 方法。
- 然后判断设置的 newVal 和之前的 value 有无不同,如果未发生改变则直接 return。
- 如果有定义的 setter 方法,则会执行直接更新 data 对象;反之,对 val 赋予新值。
- 最后对 newVal 进行 observe 观察,并通知 dep.notify 。
set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify()}
总结
上面是执行 observe 观察方法后,执行的相关过程,通过 Observer 方法类创建观察者对象,每个观察者都具备 __ob__属性。
接着通过 defineReactive 对 options.data 上的属性进行数据动态响应的设置,其核心就是通过 getter/setter 建立起观察机制。