深入浅出 Vue 系列 数据劫持实现原理

2020-06-14 06:28:58易采站长站整理


// 部分重复代码 这里就不再罗列了。
function defineReactive (obj, key) {
// ...

const desc = Object.getOwnPropertyDescriptor(obj, key)

if (desc && desc.configurable === false) {
return
}

// ...
}

而在 JavaScript 中,导致 configurable 值为 false 的情况还是很多的:

可能该属性在此之前已经通过 Object.defineProperty() 方法设置了 configurable 的值;
通过 Object.seal() 方法设置该对象为密封对象,只能修改该属性的值并且不能删除该属性以及修改属性的描述符;
通过 Object.freeze() 方法冻结该对象,相比较 Object.seal() 方法,它更为严格之处体现在不允许修改属性的值。

2、默认 getter 和 setter 方法

另外,开发者可能已经为对象的属性设置了 getter 和 setter 方法,对于这种情况,Vue 当然不能破坏开发者定义的方法,所以 Vue 中还要保护默认的 getter 和 setter 方法:


// 部分重复代码 这里就不再罗列了
function defineReactive (obj, key) {
let val = obj[key]

//....

// 默认 getter setter
const getter = desc && desc.get
const setter = desc && desc.set

Object.defineProperty(obj, key, {
get () {
const value = getter ? getter.call(obj) : val // 优先执行默认的 getter
return value
},
set (newValue) {
const value = getter ? getter.call(obj) : val
// 如果值相同则没必要更新 === 的坑点 NaN!!!!
if (newValue === value || (value !== value && newValue !== newValue)) {
return
}

if (getter && !setter) {
// 用户未设置 setter
return
}

if (setter) {
// 调用默认的 setter 方法
setter.call(obj, newValue)
} else {
val = newValue
}
}
})
}

3、递归属性值

最后一种比较重要的情况就是属性的值可能也是一个对象,那么在处理对象的属性时,需要递归处理其属性值:


function defineReactive (obj, key) {
let val = obj[key]

// ...

// 递归处理其属性值
const childObj = observe(val)

// ...
}

递归循环引用对象很容易出现递归爆栈问题,对于这种情况,Vue 通过定义 ob 对象记录已经被设置过 getter 和 setter 方法的对象,从而避免递归爆栈的问题。


function isObject (val) {
const type = val
return val !== null && (type === 'object' || type === 'function')
}

function observe (value) {
if (!isObject(value)) {
return
}

let ob
// 避免循环引用造成的递归爆栈问题