• 7.13 对象检测异常

    7.13 对象检测异常

    我们在实际开发中经常遇到一种场景,对象test: { a: 1 }要添加一个属性b,这时如果我们使用test.b = 2的方式去添加,这个过程Vue是无法检测到的,理由也很简单。我们在对对象进行依赖收集的时候,会为对象的每个属性都进行收集依赖,而直接通过test.b添加的新属性并没有依赖收集的过程,因此当之后数据b发生改变时也不会进行依赖的更新。

    了解决这一问题,Vue提供了Vue.set(object, propertyName, value)的静态方法和vm.$set(object, propertyName, value)的实例方法,我们看具体怎么完成新属性的依赖收集过程。

    1. Vue.set = set
    2. function set (target, key, val) {
    3. //target必须为非空对象
    4. if (isUndef(target) || isPrimitive(target)
    5. ) {
    6. warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
    7. }
    8. // 数组场景,调用重写的splice方法,对新添加属性收集依赖。
    9. if (Array.isArray(target) && isValidArrayIndex(key)) {
    10. target.length = Math.max(target.length, key);
    11. target.splice(key, 1, val);
    12. return val
    13. }
    14. // 新增对象的属性存在时,直接返回新属性,触发依赖收集
    15. if (key in target && !(key in Object.prototype)) {
    16. target[key] = val;
    17. return val
    18. }
    19. // 拿到目标源的Observer 实例
    20. var ob = (target).__ob__;
    21. if (target._isVue || (ob && ob.vmCount)) {
    22. warn(
    23. 'Avoid adding reactive properties to a Vue instance or its root $data ' +
    24. 'at runtime - declare it upfront in the data option.'
    25. );
    26. return val
    27. }
    28. // 目标源对象本身不是一个响应式对象,则不需要处理
    29. if (!ob) {
    30. target[key] = val;
    31. return val
    32. }
    33. // 手动调用defineReactive,为新属性设置getter,setter
    34. defineReactive###1(ob.value, key, val);
    35. ob.dep.notify();
    36. return val
    37. }

    按照分支分为不同的四个处理逻辑:

    1. 目标对象必须为非空的对象,可以是数组,否则抛出异常。
    2. 如果目标对象是数组时,调用数组的splice方法,而前面分析数组检测时,遇到数组新增元素的场景,会调用ob.observeArray(inserted)对数组新增的元素收集依赖。
    3. 新增的属性值在原对象中已经存在,则手动访问新的属性值,这一过程会触发依赖收集。
    4. 手动定义新属性的getter,setter方法,并通过notify触发依赖更新。