观察者模式(Observer)
观察者模式:定义了对象间一种一对多的依赖关系,当目标对象 Subject 的状态发生改变时,所有依赖它的对象 Observer 都会得到通知。
模式特征
一个目标者对象 Subject,拥有方法:添加 / 删除 / 通知 Observer;
多个观察者对象 Observer,拥有方法:接收 Subject 状态变更通知并处理;
目标对象 Subject 状态变更时,通知所有 Observer。
代码实现
// 目标者类
class Subject {
constructor() {
this.observers = []; // 观察者列表
}
// 添加
add(observer) {
this.observers.push(observer);
}
// 删除
remove(observer) {
let idx = this.observers.findIndex(item => item === observer);
idx > -1 && this.observers.splice(idx, 1);
}
// 通知
notify() {
for (let observer of this.observers) {
observer.update();
}
}
}
// 观察者类
class Observer {
constructor(name) {
this.name = name;
}
// 目标对象更新时触发的回调
update() {
console.log(`目标者通知我更新了,我是:${this.name}`);
}
}
// 实例化目标者
let subject = new Subject();
// 实例化两个观察者
let obs1 = new Observer('前端开发者');
let obs2 = new Observer('后端开发者');
// 向目标者添加观察者
subject.add(obs1);
subject.add(obs2);
// 目标者通知更新
subject.notify();
// 输出:
// 目标者通知我更新了,我是前端开发者
// 目标者通知我更新了,我是后端开发者
发布订阅模式(Publisher && Subscriber)
我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。
代码实现
//事件中心
export class EventEmitter {
constructor() {
/* 使用 Object.create(null) 创建的空对象,不会受到原型链的干扰。原型链终端指向 null,不会有构造函数,也不会有 toString、 hasOwnProperty、valueOf 等属性,这些属性来自 Object.prototype。所有普通对象的原型链都会指向 Object.prototype。
所以 Object.create(null) 创建的空对象比
// eg.A
let emptyObj = {};
// eg.B
let emptyObj = new Object();
两种方式,更干净,不会有 Object 原型链上的属性。 */
this._events = Object.create(null);
}
//注册监听
on(type, fn, context = this) {
if (!this._events[type]) {
this._events[type] = [];
}
this._events[type].push([fn, context]);
}
once(type, fn, context = this) {
let fired = false;
function magic() {
this.off(type, magic);
if (!fired) {
fired = true;
fn.apply(context, arguments);
}
}
this.on(type, magic);
}
//移除监听
off(type, fn) {
let _events = this._events[type];
if (!_events) {
return;
}
let count = _events.length;
while (count--) {
if (_events[count][0] === fn) {
_events[count][0] = undefined;
}
}
}
//响应监听事件
emit(type) {
let events = this._events[type];
if (!events) {
return;
}
let len = events.length;
let eventsCopy = [...events];
for (let i = 0; i < len; i++) {
let event = eventsCopy[I];
let [fn, context] = event;
if (fn) {
/* [].slice作用是返回数组中的某一段 */
/* 在js中,函数本身也是一种对象,也是具有属性和方法的,call就是其中之一。它的第一个参数,是指定函数执行时的this指针,后面的参数,就是函数执行的参数。 */
/* [].slice.call(arguments, 1)返回的是arguments数组从1号位开始的片段 */
/* call()方法 的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组. fn.call(obj, arg1, arg2, ...), 传参数列表,以逗号隔开;fn.apply(obj, [arg1, arg2, ...]), 传参数数组;*/
fn.apply(context, [].slice.call(arguments, 1));
}
}
}
}
发布订阅模式中,订阅者各自实现不同的逻辑,且只接收自己对应的事件通知。
DOM 事件监听也是 “发布订阅模式” 的应用:
let loginBtn = document.getElementById('#loginBtn');
// 监听回调函数(指定事件)
function notifyClick() {
console.log('我被点击了');
}
// 添加事件监听
loginBtn.addEventListener('click', notifyClick);
// 触发点击, 事件中心派发指定事件
loginBtn.click();
// 取消事件监听
loginBtn.removeEventListener('click', notifyClick);
观察者模式 VS 发布订阅模式
类似点
都是定义一个一对多的依赖关系,有关状态发生变更时执行相应的通知。
区别点
观察者模式对象间依赖关系较强,目标对象直接触发全部通知,观察对象被迫接收通知。
发布订阅模式更灵活,是进阶版的观察者模式。它与观察者模式的不同,在于“第三者” (事件中心)出现。目标对象并不直接通知观察者,而是通过事件中心来派发通知(只通知订阅对应事件的对象),实现了真正的解耦。