从底层代码理解Vue 响应式系统的核心机制

在 Vue.js 的响应式系统中,依赖收集和变化检测是核心机制,确保了数据的变动能够自动驱动视图更新。在上文我们已经了解了的依赖收集变化检测与更新过程,主要依赖于 WatcherDep 和响应式的 gettersetter 来实现。下面详细从底层代码解释这两个过程

1. 依赖收集

依赖收集是指 Vue 在组件渲染过程中,收集那些与视图有关的数据属性,从而将它们与当前的 Watcher 建立关联。具体步骤如下:

  • 激活 Watcher:当一个组件开始渲染时,Vue 会创建一个 Watcher 实例,这个 Watcher 用来追踪组件模板中依赖的响应式数据。

  • 触发 getter 进行依赖收集

    • 在渲染组件时,Vue 会通过访问数据(如 datacomputed)来更新 DOM。此时,响应式数据的 getter 会被触发。
    • getter 中,Vue 会将当前活跃的 Watcher(即正在渲染的组件)添加到该数据属性的 Dep 中,完成依赖收集。这意味着该数据与这个 Watcher 建立了关联。
  • Dep 的作用Dep 是一个依赖管理器,它管理与某个数据属性相关联的所有 Watcher。每个响应式属性都有一个 Dep,在 getter 中会通过 Dep 收集当前的 Watcher

    • 核心目标:为每个被访问的数据属性记录哪些 Watcher 依赖于它。这样,当数据变化时,Vue 能通知与这个数据有关的所有 Watcher
依赖收集的代码示例:
class Dep {
  constructor() {
    this.subs = []; // 订阅者列表,存储 Watcher
  }

  // 添加 Watcher 到订阅者列表
  addSub(watcher) {
    this.subs.push(watcher);
  }

  // 通知所有订阅者更新
  notify() {
    this.subs.forEach(sub => sub.update());
  }
}

class Watcher {
  constructor(updateFn) {
    this.updateFn = updateFn;
    // 记录当前的 Watcher
    Dep.target = this;
  }

  // 更新视图
  update() {
    this.updateFn();
  }
}

// 通过 getter 收集依赖
function defineReactive(obj, key, val) {
  const dep = new Dep();

  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {
        dep.addSub(Dep.target); // 依赖收集
      }
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        val = newVal;
        dep.notify(); // 通知依赖更新
      }
    }
  });
}
  • Dep.target 是一个非常重要的全局变量,它用于依赖收集阶段,记录当前正在执行的 Watcher 实例。它的作用是在访问响应式数据时,能够将这个响应式数据的依赖(即当前的 Watcher)记录下来,便于后续数据变化时通知相关的 Watcher 进行更新。

2. 变化检测与更新

变化检测与更新发生在某个数据属性的值被修改时。这个过程确保数据的变更能驱动相应的视图更新。

  • 触发 setter:当你修改一个响应式数据属性时,setter 会被触发。在 setter 中,Vue 通过 Dep 来通知所有依赖这个属性的 Watcher,告诉它们数据发生了变化。

  • 通知 Watcher 更新

    • 每个依赖该属性的 Watcher 会通过 Depnotify() 方法被通知。Dep 中存储了所有与该数据属性相关的 Watcher,它会调用每个 Watcherupdate() 方法。
  • 重新渲染视图

    • update() 被调用时,Watcher 会执行其更新函数。这通常会重新执行与视图渲染相关的逻辑,最终触发组件的重新渲染,保证视图与数据保持同步。
变化检测与更新的代码示例:
// 修改响应式数据,触发 setter
data.message = 'Hello Vue.js';

// setter 中触发 notify,通知相关 Watcher 更新视图
function defineReactive(obj, key, val) {
  const dep = new Dep();

  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {
        dep.addSub(Dep.target); // 依赖收集
      }
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        val = newVal;
        dep.notify(); // 数据变化时,通知 Watcher
      }
    }
  });
}

// Watcher 更新方法
class Watcher {
  constructor(updateFn) {
    this.updateFn = updateFn;
    Dep.target = this;
  }

  update() {
    this.updateFn(); // 重新执行视图渲染
  }
}

// 重新渲染视图的逻辑,假设渲染依赖 data.message
const renderWatcher = new Watcher(() => {
  console.log(`视图更新: ${data.message}`);
});

// 修改数据,触发视图更新
data.message = 'Hello Vue.js 3';

3. 使用响应式原理来修改属性并更新实际的 DOM

<div id="app">
  <p id="message">{{ message }}</p>
</div>
<button id="changeMessage">Change Message</button>

<script src="app.js"></script>

// Dep类,用于管理依赖
class Dep {
  constructor() {
    this.subs = [];
  }

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

  // 通知所有依赖,更新视图
  notify() {
    this.subs.forEach(sub => sub.update());
  }
}

// 全局变量,记录当前的 Watcher
Dep.target = null;

// Watcher类,用于执行更新逻辑
class Watcher {
  constructor(updateFn) {
    this.updateFn = updateFn;
    Dep.target = this;  // 在实例化时记录当前的 Watcher
    this.update();  // 初始化时调用更新函数
    Dep.target = null;  // 清空 Dep.target,防止后续误用
  }

  // 执行更新函数
  update() {
    this.updateFn();
  }
}

// 响应式处理函数
function defineReactive(obj, key, val) {
  const dep = new Dep();

  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {
        dep.addSub(Dep.target);  // 依赖收集
      }
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        val = newVal;
        dep.notify();  // 通知依赖更新
      }
    }
  });
}

// 创建响应式数据对象
const data = {};
defineReactive(data, 'message', 'Hello Vue.js');

// 获取 DOM 元素
const messageElement = document.getElementById('message');
const button = document.getElementById('changeMessage');

// Watcher,自动更新 DOM 元素
new Watcher(() => {
  messageElement.innerText = data.message;  // 更新视图中的文本
});

// 修改数据时触发视图更新
button.addEventListener('click', () => {
  data.message = 'Hello World';  // 修改数据,自动更新 DOM
});



  • 依赖收集:当 data.message 被访问时,getter 会把当前的 Watcher(通过 Dep.target 保存的)添加到 Dep 的依赖列表中。
  • 变化检测与通知:当 data.message 被修改时,setter 会调用 dep.notify(),通知所有依赖于 message 的 Watcher。
  • 自动更新 DOM:Watcher 的 update() 方法中,我们直接更新 DOM 中的 messageElement.innerText,这样在数据变化时,视图会自动更新。

总结

  • 依赖收集:在组件渲染过程中,Vue 会通过访问响应式数据触发 getter,并将当前正在渲染的 Watcher 与数据属性建立依赖关系。Dep 是一个依赖管理器,负责管理该数据属性与哪些 Watcher 相关。

  • 变化检测与更新:当数据变化时,Vue 会触发数据属性的 setter,通过 Dep 通知所有依赖该属性的 WatcherWatcher 会执行 update() 方法,从而触发视图的重新渲染。

这个过程就是 Vue 响应式系统的核心机制,确保视图和数据之间的自动同步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值