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

319 阅读5分钟

这是我参与更文挑战的第9天,活动详情查看: 更文挑战

一,前言

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

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

通过对数组中每一项调用 observe 进行递归观测,实现了对数组嵌套结构的观测(数组中嵌套数组、数组中嵌套对象)

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

与官方 Vue2 框架功能进行对比,目前代码仍需实现以下功能:

  • 对象中,老属性变更为对象、数组的情况 - 需对修改值进行深层观测处理
  • 对象中,新增属性的情况 - 需进行说明
  • 数组中,新增对象、数组、普通值的情况 - 需实现数组的方法重写并对修改值进行递归处理

综上,将以上三中情况,划分为两大部分:

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

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

备注:由于数组的递归观测尚未实现,所以本篇不包含对象的老属性变更为数组的情况;

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

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;
    }
  })
}

image.png

1,vm.message = { a: 100 }

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

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

在当前版本中,新增对象是不会被劫持的,所以不会触发视图的更新

2,vm.message.a = 200

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

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

为了实现新增对象的观测,当修改属性值且新值为对象类型时,对这个新对象调用observe方法进行深层观测,即可:

// 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);// 如果 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;
    }
  })
}

输出结果:

image.png

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

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


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

验证一下:向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;

image.png

向新增对象中添加新属性不会被观测:

  • 首先,新增对象会通过observe方法实现对象的深层观测;
  • 此时,向新增对象中添加的新属性b不会被劫持,也不会实现数据观测;

在官方 Vue2 中,为新增对象添加新属性,同样也是不能实现数据观测的;但可以通过vm.$set来实现对象中新增属性的数据观测能力;


五,结尾

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

  • 实现了对象老属性值变更为对象、数组时的深层观测处理;
  • 结合实现原理,说明了对象新增属性不能被观测到的原因,以及如何实现对象新增属性的数据观测;

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


维护日志

  • 20230111:重新梳理文章内容,对部分内容描述进行了优化,添加必要的说明性图片使表述更加完整清晰;