vue2.x 的依赖收集通知更新
Vue 的响应式系统是基于 观察者模式 设计的。当某些数据(状态)发生变化时,依赖这些数据的视图或计算属性会自动更新。为了实现这一点,Vue 会对数据进行 “劫持”(通过 Object.defineProperty 或 Proxy),并在读取数据时进行 依赖收集,在数据变化时通知所有依赖进行更新。
-
依赖收集
- 当你使用 data 中的属性时,Vue 会自动创建一个 依赖关系,将当前组件的 Watcher (观察者) 与这个属性关联起来。
- Watcher 是一个特殊的对象,它负责在数据发生变化时执行特定的函数。
- 依赖收集的过程主要通过 getter 和 setter 函数实现。
-
通知更新
- 当 data 中的属性发生变化时,Vue 会触发 setter 函数。
- setter 函数会通知所有与该属性相关的 Watcher,并执行它们的更新逻辑。
- 更新逻辑通常是重新渲染组件的模板,从而反映数据变化。
-
具体流程
- 数据访问: 当你访问 data 中的属性时,Vue 会创建一个依赖关系,将当前组件的 Watcher 与该属性关联起来。
- 依赖记录: Vue 会将 Watcher 记录到属性对应的 Dep 对象中。Dep 对象是一个专门用于管理依赖关系的对象。
- 数据变化: 当 data 中的属性发生变化时,Vue 会触发 setter 函数。
- 通知 Watcher: setter 函数会通知所有与该属性相关的 Watcher,并执行它们的更新逻辑。
- 组件更新: Watcher 的更新逻辑通常是重新渲染组件的模板,从而反映数据变化。
示例:
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue!'
}
},
mounted() {
// 访问 data 中的属性,建立依赖关系
console.log(this.message);
}
}
</script>
在这个例子中,当组件被挂载时,访问 this.message 会建立一个依赖关系,将组件的 Watcher 与 message 属性关联起来。
如果 message 属性的值发生变化,Vue 会通知相关的 Watcher,并重新渲染组件的模板,从而显示新的消息。
Vue 使用 Observer、Watcher 和 Dep 类来实现依赖收集。
依赖收集
- Observer:
- Observer 类负责将数据转换为响应式对象。它会遍历对象的所有属性,并使用defineReactive 方法为每个属性设置 getter 和 setter。
- defineReactive 方法会为每个属性创建一个 Dep 实例,用于收集依赖。
// core/observer/index.js
function observe(value, asRootData) {
if (!isObject(value)) {
return
}
let ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
return ob
}
class Observer {
constructor(value) {
this.value = value; // 保存被观察的对象
this.dep = new Dep(); // 创建一个 Dep 实例,用于收集依赖
this.vmCount = 0; // 记录有多少个 Vue 实例依赖于这个观察者
def(value, '__ob__', this); // 将观察者实例附加到对象上
if (Array.isArray(value)) {
// 处理数组的特殊情况
} else {
this.walk(value); // 遍历对象的所有属性
}
}
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]); // 为每个属性设置 getter 和 setter
}
}
}
function defineReactive(obj, key, val, customSetter, shallow) {
const dep = new Dep(); // 创建一个 Dep 实例,用于收集依赖
const property = Object.getOwnPropertyDescriptor(obj, key); // 获取属性描述符
if (property && property.configurable === false) {
return
}
const getter = property && property.get; // 获取原始的 getter
const setter = property && property.set; // 获取原始的 setter
let childOb = !shallow && observe(val); // 递归观察子属性
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val; // 获取属性值
if (Dep.target) {
dep.depend(); // 收集依赖
if (childOb) {
childOb.dep.depend(); // 收集子属性的依赖
}
if (Array.isArray(value)) {
dependArray(value); // 处理数组的依赖
}
}
return value
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val; // 获取属性值
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal); // 调用原始的 setter
} else {
val = newVal; // 更新属性值
}
childOb = !shallow && observe(newVal); // 递归观察新的子属性
dep.notify(); // 通知所有依赖更新
}
});
}
- Dep:
- Dep 类维护一个订阅者列表,记录所有依赖于该属性的 Watcher 实例。
- 每当属性被访问时,Dep 会将当前的 Watcher 添加到订阅者列表中。
// core/observer/dep.js
class Dep {
constructor() {
this.id = uid++; // 唯一标识符
this.subs = []; // 订阅者列表
}
addSub(sub) {
this.subs.push(sub); // 添加订阅者
}
removeSub(sub) {
remove(this.subs, sub); // 移除订阅者
}
depend() {
if (Dep.target) {
Dep.target.addDep(this); // 收集依赖
}
}
notify() {
const subs = this.subs.slice(); // 复制订阅者列表
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update(); // 通知所有订阅者更新
}
}
}
Dep.target = null; // 当前正在收集依赖的 Watcher
- Watcher:
- Watcher 类负责执行依赖收集和更新。它会在组件渲染时或计算属性计算时被创建。
- Watcher 实例会在访问响应式数据时被添加到相应 Dep 的订阅者列表中。
// core/observer/watcher.js
class Watcher {
constructor(vm, expOrFn, cb, options, isRenderWatcher) {
this.vm = vm; // 保存 Vue 实例
if (isRenderWatcher) {
vm._watcher = this; // 将当前 Watcher 设置为渲染 Watcher
}
vm._watchers.push(this); // 添加到 Vue 实例的 Watcher 列表中
this.cb = cb; // 回调函数
this.id = ++uid; // 唯一标识符
this.deps = []; // 依赖列表
this.newDeps = []; // 新依赖列表
this.depIds = new Set(); // 依赖的唯一标识符集合
this.newDepIds = new Set(); // 新依赖的唯一标识符集合
this.expression = expOrFn.toString(); // 表达式
this.getter = expOrFn; // 获取器函数
this.value = this.get(); // 初始值
}
get() {
pushTarget(this); // 将当前 Watcher 设置为 Dep.target
let value = this.getter.call(this.vm); // 执行获取器函数
popTarget(); // 恢复 Dep.target
this.cleanupDeps(); // 清理旧的依赖
return value
}
addDep(dep) {
const id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id); // 添加新的依赖
this.newDeps.push(dep); // 添加到新依赖列表
if (!this.depIds.has(id)) {
dep.addSub(this); // 添加订阅者
}
}
}
update() {
this.run(); // 执行更新
}
run() {
const value = this.get(); // 获取新值
if (value !== this.value || isObject(value) || this.deep) {
const oldValue = this.value; // 保存旧值
this.value = value; // 更新值
this.cb.call(this.vm, value, oldValue); // 调用回调函数
}
}
}
通知更新
通知更新是指在数据发生变化时,通知所有依赖于该数据的 Watcher,从而触发视图的更新。
- 数据变化:
- 当响应式数据发生变化时,会触发 setter 方法。
- setter 方法会调用 Dep 实例的 notify 方法,通知所有订阅者(即 Watcher 实例)。
- Watcher 更新:
- 每个 Watcher 实例会在数据变化时被通知,并调用其 update 方法。
- update 方法会调用 run 方法,重新计算依赖的数据,并触发组件的重新渲染。