测试案例
it('should proxy and be reactive', done => {
const data = { msg: 'foo' }
const vm = new Vue({
data,
template: '<div>{{ msg }}</div>'
}).$mount()
expect(vm.$data).toEqual({ msg: 'foo' })
expect(vm.$data).toBe(data)
data.msg = 'bar'
waitForUpdate(() => {
expect(vm.$el.textContent).toBe('bar')
}).then(done)
})
- 准备待观察数据
src/core/instance/state.js
function initData (vm: Component) {
let data = vm.$options.data
// _data 保存传入的data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// proxy data on instance
const keys = Object.keys(data)
let i = keys.length
while (i--) {
const key = keys[i]
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
- 在observe创建数据的监察者
按深度优先的策略进行创建
// src/core/observer/index.js
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
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)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
如果属性值为数组,会将数组的几个特定方法进行代理,目的是实现数组的变化通知
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// 重点在这数据中的变化,会触发依赖更新
// notify change
ob.dep.notify()
return result
})
})
普通属性值的响应机制建立
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 子对象优先操作
let childOb = !shallow && observe(val)
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
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #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()
}
})
}
- 触发依赖关系建立
入口在render函数中,将vue特定的模板转换成vnode的过程中,会访问依赖的模板数据,进而进入reactiveGetter方法中
参考组件创建的关键流程,了解下组件创建的流程。
在进行render前会首先创建渲染watcher, 在其初始化时,会进行观察值的第一次获取,进行调用如下函数
get () {
// 设置当前渲染上下文
pushTarget(this)
// 调用render, update patcher
// vm._update(vm._render(), hydrating)
//恢复
popTarget()
this.cleanupDeps()
return value
}
生成渲染函数
(function anonymous(
) {
with(this){return _c('div',[_v(_s(msg))])}
})
其中_c代表createElement, 负责做转换工作,生成vnode
target._s = toString
target._v = createTextVNode
_s 访问过程中会调用reactiveGetter,进而建立了依赖关系
- 数据变化,触发依赖更新
reactiveSetter-> dep.notify()