【手写 Vue2.x 源码】第二十五篇 - 数组依赖收集的原理

831 阅读1分钟

一,前言

上篇,主要介绍了 Vue 的异步更新流程,主要涉及以下几点:

  • 为什么要做异步更新
  • 异步更新的实现思路
  • 数据变更缓存的位置
  • 缓存 watcher 更新逻辑
  • vm.$nextTick 获取更新后的 dom
  • 测试异步更新

本篇,数组的依赖收集


二,数组的依赖收集

1,前情回顾

上一篇,解决了对象的依赖收集:

  • 取值时走getter,进行依赖收集;
  • 赋值时走setter,触发视图更新;

但是,数组类型是走不到Object.defineProperty

// src/observe/index.js

// 简化后的代码...
class Observer {
  constructor(value) {
    if (isArray(value)) { // 数组类型
      this.observeArray(value);
    } else {
      this.walk(value);   // 对象类型,内部调用 defineReactive
    }
  }
}

那么,数组如何做依赖收集呢?当数组数据更新时,如何触发视图更新?

本篇,继续完善数组的依赖收集;

2,数组的响应式实现

在响应式实现中,数组类型的数据是通过重写能够改变原数组的 7 个方法实现的;

收集数组所依赖的渲染watcher,当数组更新时,触发对应的watcher执行更新操作;

3,数组的依赖收集方案

对象类型的依赖收集方案:为对象的每一个属性都增加一个dep属性用于收集依赖;

那么同理,可以为数组增加dep属性用于收集依赖;

这样一来,当数组更新时,通知数组方法执行视图更新

4,数组依赖收集的入口

当前代码:

// src/observe/index.js

class Observer {
  constructor(value) {
    // 为每一个对象添加 __ob__
    Object.defineProperty(value, '__ob__', {
      value:this,
      enumerable:false
    });
    if (isArray(value)) {
      value.__proto__ = arrayMethods;
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  }

对象或数组类型的数据,都会通过new Observer创建observer实例,

所以,Observer构造函数中的value可能是数组,也可能是对象;

Observer类中,为value添加了__ob__属性(this为当前observer实例),

也就是说,每个对象或数组都具有一个__ob__属性;(对象和数组都有value.__ob__)

那么,就可以在此处,为observer实例添加dep属性;(数组或对象都拥有value.__ob__dep);

// src/observe/index.js

import Dep from "./dep";

class Observer {
  constructor(value) {
    
    // 为当前 observer 实例添加 dep 属性
    this.dep = new Dep();
    
    Object.defineProperty(value, '__ob__', {
      value:this,
      enumerable:false
    });
    
    if (isArray(value)) {
      value.__proto__ = arrayMethods;
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  }
}

这样一来,无论对象或数组,都可以通过value.__ob__.dep获取到dep,

当数组数据变化时,就可以通过dep中收集的watcher来触发视图更新操作;

5,添加 dep 后,对象的收益

上边,为对象和数组本身都增加一个dep,即value.__ob__.dep

这种方法,不仅使数组实现了依赖收集和视图更新,也使对象的本身具备了依赖收集的特性;

在之前版本,修改对象中已存在的属性可以触发更新,但新增不存在的属性是不能触发更新的;

根本原因在于,对象中已存在的属性有dep,新增加的属性没有dep,也就无法触发更新;

  • {a:1, b:2}.__ob__.dep ===> {a:1, b:2, c:3}.__ob__.dep

value.__ob__.dep为对象和数组本身添加了一个dep,也就收集了渲染watcher,

这样一来,当为对象新增属性时,就可以利用dep中收集的watcher来触发视图的更新了;

备注:这里也是 Vue.$set() 的实现原理;


三,结尾

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

  • 数组的响应式实现
  • 数组的依赖收集方案介绍
  • 数组依赖收集的入口
  • 添加 dep 后,对象的收益

下一篇,数组依赖收集的实现


更新记录

  • 20230208:添加必要的代码示例和注释说明,添加了内容中的代码高亮,添加了“对象收益”的描述分析,更新了文章摘要;