上一节我们学习了响应式的底层基础方法 Object.defineProperty,这一节我们来学习下 Vue 响应式相关中的几个重要函数:def、proxy 与 defineReactive。它们三个名虽不同,但是实际底层实现都是调用的 Object.defineProperty,对于不同目的对它进行了包装以实现各自的功能。
def
我们先来看看 def 函数,这个函数非常简单,它的作用就是在数据对象上定义一个属性值。我们来看看它的源码,它在文件 /src/core/util/lang.js 中定义
/**
* Define a property.
*/
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
var data = {}
data.id = 999 === def(data, 'id', 999); // 类似于
虽然内部也是调用的 Object.defineProperty 去定义,但是没有配置 setter/getter 方法,这就类似于 obj.xxx = yyy 的方式直接定义了属性值。
proxy
再看 proxy 函数,它的实现也很简单,只是稍微比 def 复杂些。我们看源码,它在文件 /src/core/instance/state.js 中定义
...
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
...
其中有多处调用了这个函数,随便找一处,我们来看看初始化 options.data 时 initData 中调用 proxy 函数的地方
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
...
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
...
if (props && hasOwn(props, key)) {
...
} else if (!isReserved(key)) {
proxy(vm, `_data`, key) // 遍历 data 循环调用 proxy
}
}
// observe data
observe(data, true /* asRootData */)
}
从 initData 可以看出,它是遍历 options.data 对象,每个 key 都调用一次 proxy 函数。在 proxy 中通过 Object.defineProperty 在 target 上定义了 key 属性,也就是代理了 target._data.xxx 到 target.xxx。那么换而言之,就是把 Vue 实例上的 this._data.xxx 映射到了 this.xxx 上。这里的 xxx 就是定义在 options.data 中的属性键名。这就解决了我们在选项 options.data 里定义的对象值可以直接在 this 上访问的问题。
var options = {
data: { // options.data
id: 123,
name: 'java'
},
created: function() {
// 在 initData 中通过调用 proxy 方法实现
// options.data.id => this.id === this._data.id
this.id = 999;
// options.data.name => this.name === this._data.name
this.name = 'javascript';
}
}
new Vue(options)
defineReactive
最后来看看 defineReactive 函数,它是一个非常重要的函数,它的作用是在一个对象上定义一个响应式的属性。它的实现相比 proxy 略微复杂些,来看看它在文件 /src/core/observer/index.js 中的定义
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 实例化 Dep 对象,主要存储对该属性的依赖,Dep 类后面详细研究
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 // 获取原有 getter 方法
const setter = property && property.set // 获取原有 setter 方法
if ((!getter || setter) && arguments.length === 2) {
val = obj[key] // 尚未定义 getter 方法则通过 obj[key] 获取属性的值
}
// 对属性的 值 进行响应式观察处理,这个 observe 后面再详细研究
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() // 更新属性,通知 依赖者
}
})
}
如代码中注释说明,这个 defineReactive 函数先创建一个 Dep 依赖对象,主要负责依赖收集和在修改属性值时派发更新事件,这个类后面详细说。然后调用 Object.getOwnPropertyDescriptor 获取描述符和原有 getter/setter,跟着对属性的值传给 observe 函数,它主要是对参数进行响应式观察处理,这个我们也后面详细说。
然后就是关键点,调用 Object.defineProperty 对属性值重新配置响应式的 getter/setter。对于 getter,先获得属性值,再执行 dep.depend 方法做依赖收集的操作,然后再返回属性值。对于 setter,先设置属性为新值,然后调用 observe 对新值进行响应式观察处理。
总结:
这一节我们简单研究学习 def、proxy 和 defineReactive 函数的实现原理。def 只是简单的给数据对象定义属性值;proxy 实现了 this._data.xxx 到 this.xxx 的代理;defineReactive 则实现了数据的响应式,getter 中收集依赖,setter 中通知依赖。它们的底层核心都是依靠调用 Object.defineProperty 修改配置属性的 getter/setter 来实现。
这里我们先学习必要的基础知识,其中的依赖收集与通知依赖,我们在后面章节会继续详细研究学习。