Appearance
data 响应式
javascript
// 初始化 data
function initData (vm: Component) {
let data = vm.$options.data
// 如果 data 是函数,先执行获取返回值。
// 因为 data 会被劫持实现响应式,假如 set 中直接 data[key] = newVal
// 当我们修改 data 属性时会先调用 get,然后调用 set,set 内是 data[key] = newVal
// 又会调用 get,然后调用 set,不断重复这样调用,造成栈内存溢出。
// 因此 data 会将地址值拷贝一份到 vm._data 上,当我们修改 data 属性时,
// set 内是 vm._data[key] = newVal,不会一直重复调用。
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 保证 data 是纯对象
if (!isPlainObject(data)) {
data = {}
}
// 将 vm._data 代理到 vm 上,方便取数据
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]
// method 不能和 data 属性重名
if (methods && hasOwn(methods, key)) {
// ...
}
// props 不能和 data 属性重名
if (props && hasOwn(props, key)) {
// ...
} else if (!isReserved(key)) { // 如果属性名以 $ 或 _ 开头,不会代理到 vm 上。
proxy(vm, `_data`, key) // 代理 vm._data 到 vm 上
}
}
// 实现 data 响应式
observe(data, true /* asRootData */)
}
function getData (data: Function, vm: Component): any {
return data.call(vm, vm)
}javascript
// 通过 observe 和 Observer 类的互相调用实现 data 所有层次属性的响应式。
// observe 用来决定哪些值会 new Observer(value),实现响应式,并返回实例。首先会判断
// !isObject(value) || value instanceof VNode
// 如果满足直接 return,不处理。接着判断
// hasOwn(value, '__ob__') && value.__ob__ instanceof Observer
// 如果满足说明这个 value 已经被处理过了,直接返回 ob。
// 最后判断 Array.isArray(value) || isPlainObject(value)
// 如果满足,会对数组或纯对象 new Observer()
// Observer 类中会对进入这里的 value 定义属性 __ob__,值就是 Observer 实例,
// 表示这个 value 已经被处理过了。通过 Object.defineProperty 定义并设置不可枚举,
// 如果 Observer 构造器中 value.__ob__ = this,defineReactive 中 observe(val)
// 会让这个空对象再次进入 Observer 构造器,又给这个空对象添加了 __ob__,导致死递归。
// 如果传入构造器的 value 是纯对象,Observer 类会遍历对象的每个属性并调用
// defineReactive 实现响应式。
// defineReactive 内通过 Object.defineProperty 定义存取描述符,在对象的取值
// 和赋值时都能做一些事,实现响应式。目前 get 就是返回 value,set 就是 val = newVal
// 并 observe(newVal),因为 newVal 可能是数组或纯对象,需要变成响应式。
// 如果传入构造器的 value 是数组, 因为数组的响应式并不通过 defineReactive 实现,
// Observer 类会遍历数组的每个元素并调用 observe,observe 中又只有数组或纯对象
// 才会 new Observer(value),这样不断重复调用,嵌套的数组的中只会有纯对象会被遍历属性
// 并调用defineReactive 实现响应式,这样就实现了 data 中每个对象的所有层次属性的响
// 应式。
function observe (value: any, asRootData: ?boolean): Observer | void {
// 如果不是对象或者是虚拟DOM,不进行劫持。
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 如果这个值已经被劫持过了,不进行劫持。
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
(Array.isArray(value) || isPlainObject(value))
) {
// 进行劫持
ob = new Observer(value)
}
return ob
}
// 内部调用 defineReactive 实现响应式
class Observer {
value: any;
dep: Dep;
constructor (value: any) {
this.value = value
// 通过 Object.defineProperty 往 value 上定义 __ob__ 属性, 表示被劫持了
// 值是 Observer 实例
def(value, '__ob__', this)
if (Array.isArray(value)) {
// 是数组
this.observeArray(value)
} else {
// 是纯对象
this.walk(value)
}
}
// 遍历对象的每个属性并对它们调用 defineReactive 实现响应式。
walk (obj: Object) {
const keys = Object.keys(obj)
// 每一个属性进行劫持
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
// 遍历数组每个属性并对它们调用 observe 实现数组内对象的响应式。
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
// 调用 observe,如果是数组或纯对象,observe 内会 new Observer
// 数组的话又会回调用 observeArray,最终只有对象会实现响应式。
observe(items[i])
}
}
}
function defineReactive (
obj: Object,
key: string,
val: any
) {
observe(val)
// 为该属性实现响应式
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
return value
},
set: function reactiveSetter (newVal) {
if (newVal === value) {
return
}
val = newVal
// newVal 可能是数组或纯对象,需要变成响应式。
observe(newVal)
}
})
}