Vue中的依赖收集和派发更新

Vue 的响应式系统中,依赖收集派发更新

在 Vue 的响应式系统中,依赖收集派发更新是实现数据驱动视图的核心机制。它们确保了当数据发生变化时,依赖该数据的视图或逻辑能自动更新,无需手动操作 DOM。下面详细说明这两个过程的实现原理和流程:

一、依赖收集(Dependency Collection)

定义:当响应式数据被访问时,Vue 会记录下“谁在使用这个数据”(即依赖),这个过程称为依赖收集。

核心角色
  1. Dep(依赖管理器)
    每个响应式数据(或属性)都会对应一个 Dep 实例,用于存储所有依赖该数据的观察者(Watcher)

    • 主要方法:addSub(watcher)(添加依赖)、notify()(通知所有依赖更新)。
  2. Watcher(观察者)
    代表一个“依赖”,通常对应一个组件的渲染逻辑或用户定义的 watch 回调。当数据变化时,Watcher 会触发更新(如重新渲染组件)。

收集流程(以 Vue 2 为例)
  1. 初始化响应式数据
    Vue 初始化时,会通过 Object.definePropertydata 中的属性进行劫持,重写 gettersetter

  2. 组件渲染触发依赖收集
    当组件首次渲染时,会执行其渲染函数(render),过程中会读取 data 中的属性(如 this.msg),此时会触发属性的 getter

  3. getter 中的依赖收集逻辑

    // 简化版 getter 逻辑
    Object.defineProperty(data, 'msg', {
      get() {
        // 核心:将当前活跃的 Watcher 添加到 Dep 中
        if (Dep.target) { 
          dep.addSub(Dep.target); // dep 是当前属性的 Dep 实例
        }
        return value;
      },
      // ...setter 逻辑
    });
    
    • Dep.target:全局变量,指向当前正在执行的 Watcher(如组件渲染时的 Watcher)。
  4. 完成依赖记录
    Dep 会将 Watcher 存入自身的依赖列表(subs),至此,“数据→Watcher”的映射关系建立完成。

为什么需要依赖收集?
  • 避免“无关更新”:只更新依赖变化数据的组件,而非所有组件。例如,msg 变化时,只重新渲染使用了 msg 的组件。
  • 实现精准更新:确保数据变化时,所有依赖它的地方(模板、computedwatch 等)都能被触发。

二、派发更新(Dispatch Update)

定义:当响应式数据发生变化时,Vue 会通知所有依赖该数据的 Watcher 执行更新逻辑,这个过程称为派发更新。

触发时机

当通过 this.msg = 'new value' 修改数据时,会触发属性的 setter,进而启动派发更新流程。

派发流程(以 Vue 2 为例)
  1. setter 触发更新通知

    // 简化版 setter 逻辑
    Object.defineProperty(data, 'msg', {
      set(newValue) {
        if (newValue !== value) {
          value = newValue;
          dep.notify(); // 通知所有依赖的 Watcher 更新
        }
      },
      // ...getter 逻辑
    });
    
  2. Dep 通知所有 Watcher
    dep.notify() 会遍历自身的 subs 列表(所有依赖该数据的 Watcher),并调用每个 Watcherupdate() 方法。

  3. Watcher 执行更新

    • Watcher.update() 不会立即执行更新,而是先将自身放入一个队列(异步更新队列),避免同一轮事件循环中多次更新同一 Watcher
    • 队列会在本轮事件循环的微任务阶段(通过 nextTick)统一执行,确保 DOM 只更新一次,提升性能。
  4. 执行更新逻辑

    • 对于组件渲染的 Watcher:会调用组件的 render 函数重新生成虚拟 DOM,再通过 Diff 算法更新真实 DOM。
    • 对于用户定义的 watch:会执行对应的回调函数。
异步更新队列的意义
  • 避免重复计算:例如,连续修改 this.msg = 'a'this.msg = 'b',只会触发一次更新(最终使用 'b')。
  • 提升性能:批量处理所有更新,减少 DOM 操作次数(DOM 操作是性能瓶颈)。

三、Vue 3 中的改进

Vue 3 改用 Proxy 实现响应式,依赖收集和派发更新的核心思想不变,但流程更简洁:

  1. 依赖收集:通过 Proxyget 陷阱收集依赖,无需递归劫持对象属性(Proxy 能代理整个对象)。
  2. 派发更新:通过 Proxyset/deleteProperty 陷阱触发更新,支持更多场景(如数组索引修改、新增属性)。
  3. Watcher 改名:Vue 3 中 Watcher 更名为 Effect(副作用),但作用类似。

四、代码执行流程解析

4-1. 初始化阶段
  • 创建 Vue 实例时,observe 函数会递归处理 data 中的所有属性
  • 每个属性通过 defineReactive 被转为响应式,即重写 gettersetter
  • 同时创建 Watcher 实例,关联 render 函数(视图渲染逻辑)
4-2. 依赖收集过程
  1. Watcher 初始化时,会执行 get() 方法
  2. Dep.target 被设置为当前 Watcher(全局唯一)
  3. 执行 render 函数,读取 this.data.msg,触发 msg 属性的 getter
  4. getter 中检查到 Dep.target 存在,将当前 Watcher 添加到 msg 属性对应的 Dep 实例中
  5. 依赖收集完成后,Dep.target 被重置为 null
4-3. 派发更新过程
  1. 当修改 vm.data.msg = 'Hello Reactivity' 时,触发 msg 属性的 setter
  2. setter 中调用 dep.notify(),通知所有依赖的 Watcher
  3. Dep 遍历 subs 列表,调用每个 Watcherupdate() 方法
  4. Watcher 执行 updateFn(即 render 函数),重新渲染视图

五、关键输出解析

运行代码后,控制台会输出以下内容,清晰展示整个流程:

读取属性 msg: Hello Vue
--- 执行视图渲染 ---
修改属性 msg: Hello Reactivity
--- 开始派发更新 ---
Watcher 执行更新
--- 执行视图渲染 ---
读取属性 user: [object Object]
读取属性 name: 张三
修改属性 name: 李四
--- 开始派发更新 ---
Watcher 执行更新
--- 执行视图渲染 ---
读取属性 msg: Hello Reactivity
修改属性 msg: Hello Again
--- 开始派发更新 ---
Watcher 执行更新
--- 执行视图渲染 ---
读取属性 msg: Hello Again

六、Vue 实际实现与模拟代码的差异

  1. 嵌套对象处理:实际 Vue 中会递归观察所有嵌套对象,本示例简化实现了这一点
  2. 数组支持:Vue 对数组方法进行了特殊处理(如 pushsplice),本示例未包含
  3. 异步更新:真实 Vue 中更新会通过 nextTick 异步执行,避免频繁 DOM 操作
  4. 多类型 Watcher:实际 Vue 中有渲染 Watcher、计算属性 Watcher、用户 Watcher 等多种类型

七、代码如下:

// 1. 依赖管理器(Dep):管理每个响应式数据的依赖列表
class Dep {
constructor() {
 this.subs = []; // 存储所有依赖(Watcher)
}

// 添加依赖
addSub(watcher) {
 if (watcher && !this.subs.includes(watcher)) {
   this.subs.push(watcher);
 }
}

// 通知所有依赖更新
notify() {
 console.log('--- 开始派发更新 ---');
 this.subs.forEach(watcher => {
   watcher.update(); // 调用每个Watcher的更新方法
 });
}
}

// 2. 观察者(Watcher):代表一个依赖,数据变化时执行更新
class Watcher {
/**
* @param {Object} vm - 组件实例
* @param {Function} updateFn - 数据变化时执行的回调
*/
constructor(vm, updateFn) {
 this.vm = vm;
 this.updateFn = updateFn;
 // 初始化时立即执行一次,触发依赖收集
 this.get();
}

// 触发依赖收集
get() {
 Dep.target = this; // 将当前Watcher设为全局目标
 this.updateFn.call(this.vm); // 执行更新函数(会读取响应式数据)
 Dep.target = null; // 重置全局目标
}

// 执行更新
update() {
 console.log('Watcher 执行更新');
 this.updateFn.call(this.vm); // 重新执行更新函数
}
}

// 3. 响应式处理函数:将对象转为响应式
function defineReactive(obj, key, value) {
// 递归处理嵌套对象
observe(value);

// 每个属性对应一个Dep实例
const dep = new Dep();

Object.defineProperty(obj, key, {
 get() {
   console.log(`读取属性 ${key}: ${value}`);
   // 依赖收集:如果当前有活跃的Watcher,就添加到Dep中
   if (Dep.target) {
     dep.addSub(Dep.target);
   }
   return value;
 },
 set(newValue) {
   if (newValue !== value) {
     console.log(`修改属性 ${key}: ${newValue}`);
     value = newValue;
     observe(newValue); // 新值如果是对象,也需要转为响应式
     dep.notify(); // 通知所有依赖更新
   }
 }
});
}

// 4. 递归处理对象的所有属性
function observe(obj) {
if (typeof obj !== 'object' || obj === null) {
 return;
}

// 遍历对象的所有属性,转为响应式
Object.keys(obj).forEach(key => {
 defineReactive(obj, key, obj[key]);
});
}

// 5. 模拟Vue组件实例
class Vue {
constructor(options) {
 this.data = options.data;
 this.el = options.el;
 
 // 将data转为响应式
 observe(this.data);
 
 // 创建Watcher,关联更新函数(模拟视图渲染)
 new Watcher(this, () => this.render());
}

// 模拟视图渲染
render() {
 console.log('--- 执行视图渲染 ---');
 // 这里读取响应式数据,会触发getter,进行依赖收集
 document.querySelector(this.el).textContent = this.data.msg;
}
}

// 6. 测试代码
// 初始化实例
const vm = new Vue({
el: '#app',
data: {
 msg: 'Hello Vue',
 user: {
   name: '张三'
 }
}
});

// 模拟数据更新,观察控制台输出
setTimeout(() => {
vm.data.msg = 'Hello Reactivity'; // 修改基本类型
}, 1000);

setTimeout(() => {
vm.data.user.name = '李四'; // 修改嵌套对象
}, 2000);

setTimeout(() => {
vm.data.msg = 'Hello Again'; // 再次修改
}, 3000);

八、总结

  • 依赖收集:通过 getter 记录“谁用到了数据”,建立 数据→依赖(Watcher) 的映射。
  • 派发更新:通过 setter 触发 Dep 通知所有依赖,通过异步队列批量执行更新,最终实现视图与数据同步。

通过这个简化模型,可以清晰地理解 Vue 响应式系统的核心:数据变化自动触发依赖更新,这也是 Vue 实现数据驱动视图的基础。

这两个过程共同构成了 Vue 响应式系统的核心,也是“数据驱动”理念的具体实现。理解它们有助于解决响应式相,关的 bugs(如数据更新后视图不刷新),并写出更高效的代码。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值