JavaScript设计模式:观察者模式与发布订阅模式

2019-09-30  本文已影响0人  nucky_lee

观察者模式(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 发布订阅模式

9556190-a782d67d9a942aad.jpeg
类似点

都是定义一个一对多的依赖关系,有关状态发生变更时执行相应的通知。

区别点

观察者模式对象间依赖关系较强,目标对象直接触发全部通知,观察对象被迫接收通知。

发布订阅模式更灵活,是进阶版的观察者模式。它与观察者模式的不同,在于“第三者” (事件中心)出现。目标对象并不直接通知观察者,而是通过事件中心来派发通知(只通知订阅对应事件的对象),实现了真正的解耦。

上一篇下一篇

猜你喜欢

热点阅读