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触发依赖更新。