【Vue2.x 源码学习】第二十六篇 – 数组依赖收集的实现

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

一,前言

上篇,主要介绍了数组依赖收集的原理

本篇,数组依赖收集的实现


二,对象依赖收集的总结

{}.dep => watcher

目前,“对象本身”和“对象中的每一个属性”都拥有一个 dep 属性,用于做依赖收集
此时,为对象新增一个不存在的新属性时,就可以找到对象上的 dep 通知对应watcher做视图更新了

之前:对象本身没有 dep,只有修改了对象中已经存在的属性才会触发更新
现在:对象本身就有 dep,新增对象属性可以通知 dep 中收集的 watcher 更新


三,数组依赖收集的位置

对象或数组类型会通过 new Observer 创建 observer 实例,
所以,Observer 中的 value 可能是数组,也可能是对象;

Observer 类中的 value,即 this 指 observer 实例,
为其添加 `__ob__` 属性,这样每个对象本身或数组就拥有了 __ob__ 属性;

因此,可在此处为 observer 实例添加 dep 属性,
这就相当于为数组或对象本身都增加了一个 dep 属性;

这样就可以在对象或数组上,通过`value.__ob__.dep` 取到 dep,
当数组数据变化时,可以通过 dep 中收集的 watcher 触发视图更新操作;
复制代码

四,数组和对象本身做依赖收集

在使用 defineReactive 定义属性时,此时value值有可能是数组
对数组的取值会走 Object.defineProperty 的 get方法

而 get 方法中就会进行依赖收集,如果当前value值为数组,就进行依赖收集
所以,当取值时,会对数组和对象本身进行一次依赖收集
复制代码
// src/observe/index.js

/**
 * 给对象Obj,定义属性key,值为value
 *  使用Object.defineProperty重新定义data对象中的属性
 *  由于Object.defineProperty性能低,所以vue2的性能瓶颈也在这里
 * @param {*} obj 需要定义属性的对象
 * @param {*} key 给对象定义的属性名
 * @param {*} value 给对象定义的属性值
 */
function defineReactive(obj, key, value) {
  // childOb 是数据组进行观测后返回的结果,内部 new Observe 只处理数组或对象类型
  let childOb = observe(value);
  let dep = new Dep();  // 为每个属性添加一个 dep
  Object.defineProperty(obj, key, {
    get() {
      // 对象属性的依赖收集
      if(Dep.target){
        dep.depend();
      }
      // 数组或对象本身的依赖收集
      if(childOb){ // 如果 childOb 有值,说明数据是数组或对象类型
        // observe 方法中,会通过 new Observe 为数组或对象本身添加 dep 属性
        childOb.dep.depend();    // 让数组和对象本身的 dep 记住当前 watcher
      }
      return value;
    },
    set(newValue) {
      if (newValue === value) return
      observe(newValue);
      value = newValue;
      dep.notify(); // 对象属性的更新
    }
  })
}
复制代码
默认情况下,会为对象本身或数组本添加一个 dep 属性,
当进行观测时,会拿到数组的 observer 实例,即 childOb,`childOb.dep` 就是 dep;

在页面对数组进行取值时,如{{arr}} 一定会走 get 方法
如果 childOb 有值,就让当前数组把依赖收集起来`childOb.dep.depend()`
这样就完成了数组的依赖收集
复制代码

五,数组中嵌套对象(对象或数组)的递归处理

数组中有可能嵌套数组或对象:如[{}]或[[]]

当前只会对数组的外层进行依赖收集,数组中嵌套的数组不会进行依赖收集

注意:此时,数组中嵌套的对象是可以进行依赖收集的

数组中嵌套对象的依赖收集原理

例如:arr:[{a:1},{b:2}]

当对 arr 取值时{{arr}},默认会对 arr 进行 JSON.stringify(arr),
JSON.stringify 会取出内部所有属性进行打印输出
即 JSON.stringify 会对内部属性进行取值操作,此时会走 getter,
而 getter 中就会为对象本身和内部属性进行依赖收集

所以,这种情况默认就会进行依赖收集
复制代码

数组中嵌套数组的依赖收集实现

例如:arr:[[1][2]]

当对 arr 取值时{{arr}},只对外层数组进行依赖收集,内部的数组没有进行依赖收集
所以,arr[0].push直接操作内部数组,是不会触发视图更新的
复制代码

需要对数组类型做递归依赖收集

数组中如果有对象[{}],也需要为对象本身做依赖收集,
因为未来有可能会为对象新增属性,对象本身做依赖收集才可以更新视图
复制代码

注意:前面虽然已经对数组进行了递归观测,但用户使用数据不是递归使用的

// src/observe/index.js

function defineReactive(obj, key, value) {
  let childOb = observe(value);
  let dep = new Dep();
  Object.defineProperty(obj, key, {
    get() {
      if(Dep.target){
        dep.depend();
      }
      if(childOb){
        childOb.dep.depend();
        if(Array.isArray(value)){// 如果当前数据是数组类型
          dependArray(value)     // 可能数组中继续嵌套数组,需递归处理
        }  
      }
      return value;
    },
    set(newValue) {
      if (newValue === value) return
      observe(newValue);
      value = newValue;
      dep.notify();
    }
  })
}

/**
 * 使数组中的引用类型都进行依赖收集
 * @param {*} value 需要做递归依赖收集的数组
 */
function dependArray(value) {// 让数组里的引用类型都收集依赖
  // 数组中如果有对象:[{}]或[[]],也要做依赖收集(后续会为对象新增属性)
  for(let i = 0; i < value.length; i++){
    let current = value[i];
    // current 上如果有__ob__,说明是对象,就让 dep 收集依赖(只有对象上才有 __ob__)
    current.__ob__ && current.__ob__.dep.depend();
    // 如果内部还是数组,继续递归处理
    if(Array.isArray(current)){
      dependArray(current)
    }
  }
}
复制代码

三,结尾

本篇,主要介绍了数组依赖收集的原理

下一篇,Vue 生命周期的实现

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享