1. 数据结构
- Dep类:每个被侦测的数据(响应式数据)都有一个对应的
Dep
实例。它用于收集依赖(Watcher实例),内部使用一个Set
来存储依赖,这样可以保证依赖的唯一性。例如:
class Dep {
constructor() {
this.subs = new Set();
}
addSub(watcher) {
this.subs.add(watcher);
}
removeSub(watcher) {
this.subs.delete(watcher);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
- Watcher类:代表一个依赖,它实例化时会将自己添加到正在读取的响应式数据的
Dep
中。Watcher
实例可以是渲染watcher(负责更新视图)、计算属性watcher或者用户自定义的watcher。它有一个update
方法,当依赖的数据变化时会被调用。例如:
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
this.cb = cb;
this.getter = parsePath(expOrFn);
this.value = this.get();
}
get() {
Dep.target = this;
const vm = this.vm;
let value = this.getter.call(vm, vm);
Dep.target = null;
return value;
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb.call(this.vm, this.value, oldValue);
}
}
- Observer类:用于将普通对象转换为响应式对象,给对象的每个属性都定义
getter
和setter
。在getter
中进行依赖收集,在setter
中通知依赖更新。例如:
class Observer {
constructor(value) {
this.value = value;
this.walk(value);
}
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]]);
}
}
}
function defineReactive(data, key, val) {
const dep = new Dep();
const childOb = observe(val);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
if (Dep.target) {
dep.addSub(Dep.target);
if (childOb) {
childOb.dep.addSub(Dep.target);
}
}
return val;
},
set: function reactiveSetter(newVal) {
if (newVal === val) return;
val = newVal;
childOb = observe(newVal);
dep.notify();
}
});
}
function observe(value) {
if (!value || typeof value!== 'object') {
return;
}
return new Observer(value);
}
2. 主要逻辑步骤
- 初始化阶段:
- 当Vue实例创建时,会对data选项中的数据通过
Observer
类进行转换,将其变为响应式数据,每个属性都关联一个Dep
实例。
- 同时,会根据模板编译生成渲染watcher,计算属性也会生成对应的计算属性watcher,用户自定义watcher通过
$watch
创建。这些watcher实例在创建时会立即求值(调用get
方法)。
- 依赖收集阶段:
- 以渲染watcher为例,在求值过程中(例如访问
vm.message
),会触发message
属性的getter
。
- 在
getter
中,由于Dep.target
此时指向当前的渲染watcher,所以会将该watcher添加到message
属性对应的Dep
实例中,完成依赖收集。如果message
属性值是一个对象,还会将watcher添加到这个对象对应的Observer
实例的dep
中(用于监听对象内属性的新增和删除)。
- 数据更新阶段:
- 当数据发生变化(例如
vm.message = 'new value'
),会触发message
属性的setter
。
- 在
setter
中,首先检查新值与旧值是否相同,不同则更新值,并通知Dep
实例。
Dep
实例调用notify
方法,遍历其收集的所有watcher实例,调用它们的update
方法。
watcher
的update
方法会重新求值(重新渲染视图或者重新计算计算属性值等),从而实现数据变化后的更新操作。