美文网首页
JavaScript设计模式:观察者模式与发布订阅模式

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

作者: nucky_lee | 来源:发表于2019-09-30 16:43 被阅读0次

    观察者模式(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
    类似点

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

    区别点

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

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

    相关文章

      网友评论

          本文标题:JavaScript设计模式:观察者模式与发布订阅模式

          本文链接:https://www.haomeiwen.com/subject/fnhhpctx.html