Bootstrap

【Vue2.x 源码学习】第九篇 - 对象数据变化的观测情况

一,前言

上篇,主要介绍了数组深层观测的实现,核心几个点如下:

最初仅对数组类型进行了原型方法重写,并未进行递归处理,所以,当时仅实现了数组的单层劫持;

通过对数组进行 observe 递归观测,实现了对数组嵌套结构的劫持(数组中嵌套数组、数组中嵌套对象)

但是,由于 observe 方法对非对象类型不进行处理,所以,数组中的值类型将不会被劫持;

与 Vue2.x 功能进行对比,目前代码仍需实现以下功能:

  • 对象中,老属性变更为对象、数组的情况 - 需对修改值进行深层观测处理

  • 对象中,新增属性的情况 - 需进行说明

  • 数组中,新增对象、数组、普通值的情况 - 需实现数组的方法重写并对修改值进行递归处理

综上,将其划分为两大部分:

  • 对象数据变化:对象中,老属性变更为对象;对象中,新增属性的情况;

  • 数组数据变化:对象中,老属性变更为数组;数组中,新增对象、数组、普通值的情况;

本篇,对象数据变化的观测情况(老属性变更为对象、新增属性的情况)

由于数组的递归劫持尚未实现,故本篇不包含对象老属性变更为数组的情况;

将“对象老属性变更为数组”和“数组数据变化的观测情况”进行合并;

20210610:TODO-思考了一下,“对象中,老属性变更为数组的情况”应该被放到本篇“对象数据变化的观测情况”一起,理由是:即便此时还没有为对象属性修改为数组做递归观测,也应该被归类为“对象数据变化”,且后续“数组数据”实现递归观测后,这个问题也会一并得到解决;

二,对象中,老属性变更为对象的观测问题

let vm = new Vue({
  el: '#app',
  data() {
    return { message: 'Hello Vue', obj: { key: "val" }, arr:[1,2,3]} // data 返回一个对象
  }
});

// 将属性 message 由普通值,变更为对象类型
vm.message = { a: 100 }
// 对新增对象属性进行修改
vm.message.a = 200;

// src/observe/index.js#defineReactive

function defineReactive(obj, key, value) {
  observe(value);
  Object.defineProperty(obj, key, {
    get() {
      return value;
    },
    set(newValue) {
      console.log("修改了被观测属性 key = " + key + ", newValue = " + JSON.stringify(newValue))
      if (newValue === value) return
      value = newValue;
    }
  })
}

1,vm.message = { a: 100 }

将 data 中 message 属性由初始值"Hello Vue",变更为对象类型 { a : 100 }

由于 { a : 100 } 是 data 初始化完成后的新增对象,属于对象中新增对象

而当前版本中的新增对象不会被劫持,也就不会触发视图的更新

2,vm.message.a = 200

由于对象 { a : 100 } 没有被观测,所以修改此对象中的属性时,不会触发视图的更新

三,对象中,老属性变更为对象的观测实现

为了实现新增对象的数据观测,当设置的值为对象时,将此对象继续进行深层观测即可;

// src/observe/index.js#defineReactive

/**
 * 给对象Obj,定义属性key,值为value
 *  使用Object.defineProperty重新定义data对象中的属性
 *  由于Object.defineProperty性能低,所以vue2的性能瓶颈也在这里
 * @param {*} obj 需要定义属性的对象
 * @param {*} key 给对象定义的属性名
 * @param {*} value 给对象定义的属性值
 */
function defineReactive(obj, key, value) {
  observe(value);// 递归实现深层观测
  Object.defineProperty(obj, key, {
    get() {
      return value;
    },
    set(newValue) {
      console.log("修改了被观测属性 key = " + key + ", newValue = " + JSON.stringify(newValue))
      if (newValue === value) return
      // 当值被修改时,通过 observe 实现对新值的深层观测,此时,新增对象将被观测
      observe(newValue);
      value = newValue;
    }
  })
}

输出结果:

这样,就实现了对象中新增对象的深层观测

同时,对新增对象中的属性再次进行修改时,也能够通过数据劫持实现视图更新了

四,对象中,新增属性的情况

向 message 属性的新增对象 { a:100 } 中,添加一个不存在的属性 b,是否会进行数据劫持?

let vm = new Vue({
  el: '#app',
  data() {
    return { message: 'Hello Vue', obj: { key: "val" }, arr:[1,2,3]}
  }
});

// 属性 message 由“普通值” 变更为 “对象”类型
vm.message = { a: 100 }
// 向新增对象中添加一个不存在的属性
vm.message.b = 200;
// 修改新增属性值
vm.message.b = 300;

之前新增对象时,通过 observe 方法对新增对象进行了深层观测

而此时,向新增对象中添加新属性 b 并不会被劫持,也不会触发任何为其添加数据劫持的逻辑

所以,向新增对象中添加新属性将不会被劫持;

在 Vue2.x 中,为新增对象添加新属性,也是不能实现数据劫持的;

但可以通过 vm.$set 来实现对象中新增属性的数据观测能力;

五,结尾

又成功水了一篇,还剩 12 篇

本篇,主要介绍了数组数据变化的观测情况:

  • 实现了对象老属性值变更为对象、数组时的深层观测处理;

  • 结合实现原理,说明了对象新增属性不能被观测的原因,及如何实现数据观测;

下一篇,数组数据变化的观测情况(对象中,老属性变更为数组;数组中,新增对象、数组、普通值的情况;)