7.13 对象检测异常
我们在实际开发中经常遇到一种场景,对象test: { a: 1 }
要添加一个属性b
,这时如果我们使用test.b = 2
的方式去添加,这个过程Vue
是无法检测到的,理由也很简单。我们在对对象进行依赖收集的时候,会为对象的每个属性都进行收集依赖,而直接通过test.b
添加的新属性并没有依赖收集的过程,因此当之后数据b
发生改变时也不会进行依赖的更新。
了解决这一问题,Vue
提供了Vue.set(object, propertyName, value)
的静态方法和vm.$set(object, propertyName, value)
的实例方法,我们看具体怎么完成新属性的依赖收集过程。
Vue.set = set
function set (target, key, val) {
//target必须为非空对象
if (isUndef(target) || isPrimitive(target)
) {
warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
}
// 数组场景,调用重写的splice方法,对新添加属性收集依赖。
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val
}
// 新增对象的属性存在时,直接返回新属性,触发依赖收集
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val
}
// 拿到目标源的Observer 实例
var ob = (target).__ob__;
if (target._isVue || (ob && ob.vmCount)) {
warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
);
return val
}
// 目标源对象本身不是一个响应式对象,则不需要处理
if (!ob) {
target[key] = val;
return val
}
// 手动调用defineReactive,为新属性设置getter,setter
defineReactive###1(ob.value, key, val);
ob.dep.notify();
return val
}
按照分支分为不同的四个处理逻辑:
- 目标对象必须为非空的对象,可以是数组,否则抛出异常。
- 如果目标对象是数组时,调用数组的
splice
方法,而前面分析数组检测时,遇到数组新增元素的场景,会调用ob.observeArray(inserted)
对数组新增的元素收集依赖。 - 新增的属性值在原对象中已经存在,则手动访问新的属性值,这一过程会触发依赖收集。
- 手动定义新属性的
getter,setter
方法,并通过notify
触发依赖更新。