跳到主要内容

依赖收集

在 Vue.js 中,依赖收集是 Vue 响应式系统的一个重要概念,用于追踪组件中响应式数据的依赖关系,以便在数据发生变化时能够触发相关的更新操作。Vue 的依赖收集是基于 JavaScript 的 getter 和 setter 机制实现的。

基本原理

1. 响应式数据

Vue 中的响应式数据是通过 Vue 实例的 data 属性进行定义的,或者通过 Vue.observable() 方法创建的响应式对象。当组件使用响应式数据时,Vue 会将这些数据转换成 getter 和 setter,以便在访问和修改数据时能够进行依赖收集。

2. 依赖对象 (Dep)

假设,vue 将 data 中的所有数据都设置为响应式数据,那么,当 text3 被修改后,也会导致 setter 触发重新渲染。这样显然是不合理的!

new Vue({
template:
`<div>
<span>text1:</span> {{text1}}
<span>text2:</span> {{text2}}
<div>`,
data: {
text1: 'text1',
text2: 'text2',
text3: 'text3' // text3 不是响应式
}
});

Vue 中使用一个名为 Dep 的类来表示依赖关系。每个响应式数据都会关联一个 Dep 对象,用于存储依赖于该数据的 Watcher 对象。

Dep 对象有两个主要方法:

  • depend(): 用于在当前正在计算的 Watcher 对象中收集依赖。
  • notify(): 用于通知所有依赖于当前数据的 Watcher 对象进行更新。
class Dep {
constructor() {
this.subscribers = new Set(); // 存储依赖于该数据的 Watcher 对象
}

depend() {
if (activeWatcher) {
this.subscribers.add(activeWatcher); // 将当前正在计算的 Watcher 对象添加到依赖集合中
}
}

notify() {
this.subscribers.forEach(subscriber => subscriber.update()); // 通知所有依赖于该数据的 Watcher 对象进行更新
}
}

3. Watcher 对象

Watcher 对象用于观察数据变化,并在数据发生变化时执行相应的回调函数。当一个组件渲染时,Vue 会创建一个 Watcher 对象来监听组件中的响应式数据,当数据发生变化时,Watcher 对象会收到通知并触发重新渲染。

  • Watcher 类表示一个观察者对象,用于观察数据变化,并在数据发生变化时执行相应的回调函数。
  • get() 方法用于触发依赖收集,将当前 Watcher 对象设置为活动状态,然后调用 Vue 实例的 _render() 方法渲染组件并触发依赖收集,最后清空当前 Watcher 对象。
class Watcher {
constructor(vm, update) {
this.vm = vm; // Vue 实例
this.update = update; // 数据变化时的回调函数
}

get() {
activeWatcher = this; // 设置当前正在计算的 Watcher 对象
this.vm._render(); // 渲染组件并触发依赖收集
activeWatcher = null; // 清空当前正在计算的 Watcher 对象
}
}

4. 依赖收集过程

当一个组件渲染时,Vue 会创建一个 Watcher 对象来监听组件中响应式数据的变化。在组件渲染的过程中,当访问响应式数据时,Watcher 对象会调用数据的 getter 方法,同时 Dep 对象的 depend() 方法会被调用,用于收集当前 Watcher 对象作为依赖。这样,当数据发生变化时,Vue 就能够知道哪些 Watcher 对象依赖于该数据,并且能够及时地通知它们进行更新。

  • observe 函数用于将对象转换成响应式对象。它通过 Object.defineProperty() 方法为对象的每个属性定义 getter 和 setter,以便在访问和修改数据时触发依赖收集和更新操作。
  • 在 getter 中调用 dep.depend() 方法来收集依赖,将当前活动的 Watcher 对象添加到依赖集合中。
  • 在 setter 中如果发现数据发生变化,则更新数据,并调用 dep.notify() 方法通知所有依赖的 Watcher 对象进行更新。
// 定义 Dep 类表示依赖对象
class Dep {
constructor() {
this.subscribers = new Set(); // 存储依赖于该数据的 Watcher 对象
}

depend() {
if (activeWatcher) {
this.subscribers.add(activeWatcher); // 将当前正在计算的 Watcher 对象添加到依赖集合中
}
}

notify() {
this.subscribers.forEach(subscriber => subscriber.update()); // 通知所有依赖于该数据的 Watcher 对象进行更新
}
}

// 定义 Watcher 类表示观察者对象
class Watcher {
constructor(vm, update) {
this.vm = vm; // Vue 实例
this.update = update; // 数据变化时的回调函数
}

get() {
activeWatcher = this; // 设置当前正在计算的 Watcher 对象
this.vm._render(); // 渲染组件并触发依赖收集
activeWatcher = null; // 清空当前正在计算的 Watcher 对象
}
}

// 定义 observe 函数用于将对象转换成响应式对象
function observe(obj) {
Object.keys(obj).forEach(key => {
let internalValue = obj[key];
const dep = new Dep(); // 创建依赖对象
Object.defineProperty(obj, key, {
get() {
dep.depend(); // 收集依赖
return internalValue;
},
set(newValue) {
if (newValue !== internalValue) {
internalValue = newValue;
dep.notify(); // 通知更新
}
}
});
});
return obj;
}

// 定义 Vue 类
class Vue {
constructor(options) {
this._data = observe(options.data()); // 将 data 属性转换成响应式对象
}

_render() {
// 渲染组件
}
}

// 使用示例
const vm = new Vue({
data() {
return {
count: 0
};
}
});

// 创建 Watcher 对象
const watcher = new Watcher(vm, () => {
console.log('count changed:', vm._data.count);
});

// 触发依赖收集和更新
vm._data.count++;