基本概念
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其依赖者(观察者)会自动收到通知并更新。
在前端开发中,观察者模式常被用来实现组件间的数据传递和事件处理。比如,当一个组件的状态发生改变时,可以通过观察者模式来通知其他组件更新自身的状态或视图。
在观察者模式中,通常会定义两种角色:
1. Subject(主题):它是被观察的对象,当其状态发生改变时会通知所有的观察者。
1. Observer(观察者):它是观察主题的对象,当主题状态发生改变时会接收到通知并进行相应的处理。
观察者模式示例
// 定义主题对象
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
// this.observers = this.observers.filter(item => item !== observer);
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
notify(data) {
this.observers.forEach(observer => {
observer.update(data);
});
}
}
// 定义观察者对象
class Observer {
constructor(name) {
this.name = name;
}
update(data) {
console.log(`${this.name} received data: ${data}`);
// 处理接收到的数据
}
}
// 使用示例
const subject = new Subject();
const observer1 = new Observer('observer1');
const observer2 = new Observer('observer2');
subject.addObserver(observer1);
subject.addObserver(observer2);
// 通知所有观察者
subject.notify("Hello World");
// observer1 received data: Hello World
// observer2 received data: Hello World
在上述示例中,我们定义了一个主题对象 Subject
和一个观察者对象 Observer
。主题对象负责维护一组观察者对象,并在状态变化时通知观察者。观察者对象通过注册到主题对象中,接收到主题的通知后进行相应的处理。
Vue2 双向绑定
Vue2 源码通过观察者模式实现了双向数据绑定。下面是 Vue2 源码中实现双向绑定的大致代码实现:
- 首先,Vue 通过 Object.defineProperty 方法对数据对象进行劫持,将其转化为响应式对象。在这个过程中,Vue 会为每个属性创建一个 Dep 对象,用于收集依赖和通知更新。
- 在模板编译阶段,Vue 会解析模板中的指令和表达式,并创建对应的指令对象。每个指令对象都会关联一个 Watcher 对象。
- Watcher 对象负责订阅数据变化,并在数据变化时执行相应的回调函数。它会将自身添加到相关属性的依赖(Dep)中。
- 当数据发生变化时,被劫持的属性会触发相应的 setter 函数。在这个过程中,属性关联的依赖(Dep)会通知所有订阅者(即相关的Watcher)进行更新。
- 更新过程中,订阅者(即相关的Watcher)会执行回调函数,并更新视图。
- 定义观察者对象
// 定义观察者对象 class Watcher { constructor(vm, key, updateFn) { this.vm = vm; this.key = key; this.updateFn = updateFn; Dep.target = this; this.vm[this.key]; // 触发 getter,收集依赖 Dep.target = null; } // 更新视图 update() { this.updateFn.call(this.vm, this.vm[this.key]); } }
- 定义依赖对象
// 定义依赖对象 class Dep { constructor() { this.subscribers = []; } // 收集依赖 depend() { if (Dep.target && !this.subscribers.includes(Dep.target)) { this.subscribers.push(Dep.target); } } // 通知更新 notify() { this.subscribers.forEach((subscriber) => subscriber.update()); } }
- 定义响应式数据劫持函数
// 定义响应式数据劫持函数 function defineReactive(obj, key, val) { const dep = new Dep(); Object.defineProperty(obj, key, { get() { // 收集依赖 dep.depend(); return val; }, set(newVal) { if (newVal !== val) { val = newVal; // 通知更新 dep.notify(); } }, }); }
- 创建 Vue 实例
// 创建Vue实例 class Vue { constructor(options) { this.data = options.data; for (let key in this.data) { defineReactive(this.data, key, this.data[key]); } new Watcher(this, options.key, options.updateFn); } }
以上是对 Vue2 源码实现双向绑定的简要描述。定义了 Watcher 观察者对象和 Dep 依赖对象。在defineReactive 函数中,我们使用 Object.defineProperty 对数据对象进行劫持,当数据发生变化时,会触发相应的 getter 和 setter 函数。在 getter 函数中,我们收集依赖;在 setter 函数中,我们通知依赖进行更新。通过创建 Vue 实例时创建的观察者对象,我们实现了数据和视图之间的双向绑定。