vue2底层原理解析(易懂版)

核心机制

1. 数据劫持(Data Observation)

通过劫持对象属性的读写操作,在 读属性时收集依赖,在 写属性时触发更新

2. 依赖收集(Dependency Collection)

每个被劫持的属性都有一个 Dep 实例,负责管理所有依赖(Watcher)。

3. 派发更新(Dependency Notification)

当属性被修改时,通过 Dep 通知所有依赖(Watcher)执行更新。


Vue 2 的实现(基于 Object.defineProperty)

1. 数据劫持
class Dep {
  constructor() {
    this.subs = new Set(); // 存储 Watcher 实例
  }
  depend() {
    if (Dep.target) {
      this.subs.add(Dep.target); // 收集依赖
    }
  }
  notify() {
    this.subs.forEach(watcher => watcher.update()); // 触发更新
  }
}
Dep.target = null; // 全局变量,指向当前正在计算的 Watcher

function defineReactive(obj, key) {
  const dep = new Dep();
  let value = obj[key];
  Object.defineProperty(obj, key, {
    get() {
      dep.depend(); // 收集依赖
      return value;
    },
    set(newVal) {
      if (newVal === value) return;
      value = newVal;
      dep.notify(); // 触发更新
    }
  });
}
2. Watcher(依赖观察者)
class Watcher {
  constructor(getter) {
    this.getter = getter;
    this.value = this.get();
  }
  get() {
    Dep.target = this; // 设置全局 Watcher
    const value = this.getter(); // 触发属性的 getter,收集依赖
    Dep.target = null;
    return value;
  }
  update() {
    this.get(); // 更新逻辑(实际中会触发视图更新)
  }
}
3. 使用示例
const data = { count: 0 };
defineReactive(data, 'count');

new Watcher(() => {
  console.log('Count changed:', data.count);
});

data.count = 1; // 控制台输出: "Count changed: 1"

代码片段解析

get() {
  Dep.target = this;          // 关键点1:将当前 Watcher 实例挂载到全局 Dep.target
  const value = this.getter();// 关键点2:执行 getter 函数,触发数据属性的 getter
  Dep.target = null;          // 关键点3:清除全局 Dep.target
  return value;
}

核心逻辑拆解

关键点1:Dep.target = this
  • 作用:将当前 Watcher 实例(例如一个组件的渲染函数、计算属性或侦听器)临时存储到全局变量 Dep.target
  • 为什么需要全局变量
    • 数据属性的 getter 被触发时,需要知道“当前是谁在访问这个属性”,这样才能将依赖(Watcher)收集到对应的 Dep 中。
    • Dep.target 就像一个“登记处”,告诉系统:“当前正在执行的是这个 Watcher,如果访问了响应式数据,请把我和这些数据关联起来”。
关键点2:this.getter()
  • getter 是什么
    • getter 是一个函数,通常是需要观察的表达式或函数。例如:
      • 组件的渲染函数(访问模板中用到的数据)。
      • 计算属性的计算函数(如 () => this.a + this.b)。
      • 用户自定义的 watch 回调。
  • 执行 getter 的作用
    • getter 执行时,会访问响应式数据(如 data.count),触发数据属性的 getter
    • 数据属性的 getter 中会调用 dep.depend(),将当前全局的 Dep.target(即当前 Watcher)收集为依赖。
关键点3:Dep.target = null
  • 作用:清除全局的 Dep.target,避免后续非 Watcher 逻辑误触发依赖收集。
  • 必要性
    • JavaScript 是单线程的,同一时间只能有一个 Watcher 在执行。
    • 通过 Dep.target = null,确保只有在 Watcher 的 get() 方法执行期间,依赖收集才会发生。

完整流程示例

假设有以下代码:

const data = { count: 0 };
defineReactive(data, 'count'); // 将 data.count 转换为响应式

// 创建一个 Watcher,观察 data.count
const watcher = new Watcher(() => {
  console.log('Current count:', data.count);
});
步骤1:初始化 Watcher
  • 调用 new Watcher() 时,会执行 this.get() 方法。
  • Dep.target 被设置为当前 watcher 实例。
步骤2:执行 getter 函数
  • getter 函数 () => { console.log(data.count); } 开始执行。
  • 访问 data.count,触发其 getter
步骤3:数据属性的 getter 逻辑
  • data.countgetter 中,调用 dep.depend()
  • 此时 Dep.target 指向当前 watcher,于是将这个 watcher 添加到 data.count 的依赖列表(dep.subs)中。
步骤4:完成依赖收集
  • getter 执行完毕,Dep.target 被重置为 null
  • 此时 data.count 的依赖列表中已经记录了 watcher
步骤5:数据变更触发更新
  • 当执行 data.count = 1 时,触发 data.countsetter
  • setter 调用 dep.notify(),通知所有依赖(即 watcher)执行 update() 方法。
  • watcher.update() 会再次执行 get() 方法,重新计算值并触发更新。

为什么需要这种设计?

依赖收集的动态性
  • 每次执行 getter 时,访问的响应式数据可能不同(例如条件分支 if (condition) a else b)。
  • 通过动态设置 Dep.target,可以确保每次计算时,只有实际访问到的数据的依赖被收集。
避免重复依赖
  • 如果 Dep.target 未被及时清除,可能导致同一个 Watcher 被重复收集到多个 Dep 中,造成不必要的更新。

类比现实场景

假设你是一个图书馆管理员(Watcher),需要统计读者(响应式数据)的借阅记录:

  1. 登记身份Dep.target = this):

    • 你进入图书馆时,在登记处写下自己的名字(相当于设置 Dep.target),表示“当前是我在借书”。
  2. 借书操作(执行 getter):

    • 你借了一本书《Vue 原理》(访问 data.count),图书馆系统会自动记录:“这本书被管理员借走了”。
  3. 离开图书馆Dep.target = null):

    • 你离开时擦掉登记处的名字,确保后续其他人的借阅不会被误记录到你名下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值