再次理解订阅发布模式

之前的总结:设计模式之发布订阅模式

为什么会有这种设计模式

这里有个很好的回答:https://segmentfault.com/q/1010000002487388

简单的基于对象的订阅发布模式

function EventEmitter() {
            this._events = {};
        }
        EventEmitter.prototype.addListener = function (type, listener) {
            if (!this._events[type]) this._events[type] = [];
            this._events[type].push(listener);
        }
        EventEmitter.prototype.removeListener = function (type, listener) {
            var listenerArray = this._events[type] ? this._events[type] : [];
            var index = listenerArray.indexOf(listener);
            if (index !== -1) {
                listenerArray.splice(index, 1);
            }
        }
        EventEmitter.prototype.emit = function (type) {
            var listenerArray = this._events[type] ? this._events[type] : [];
            for (var i = 0; i < listenerArray.length; i++) {
                listenerArray[i].apply(this);
            }
        }
        //使用
        var a = new EventEmitter();
        a.addListener("type1", function () {
            console.log("service1");
        })

        a.addListener("type1", function () {
            console.log("service2");
        })

        a.emit("type1"); // 

        function otherService() {
            console.log("other service");
        }

        a.addListener("type2", otherService);
        a.addListener("type2", function () {
            console.log("service 3")
        })
        a.emit("type2");
        a.removeListener("type2", otherService);
        a.emit("type2");

原生web api基于DOM元素的发布订阅

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="ele"></div>
    <script>
        // 基于对象的订阅/发布

        // 基于DOM元素的事件订阅/发布
        var event = new CustomEvent('add', {
            detail: 'hello'
        });

        var handle = function (e) {
            console.log('handle:' + e.detail);
        }

        var handle2 = function (e) {
            console.log('handle2:' + e.detail);
        }

        ele.addEventListener('add', handle)

        ele.addEventListener('add', handle2)

        ele.dispatchEvent(event); // handle:hello    handle2:hello

        ele.removeEventListener('add', handle2);

        ele.dispatchEvent(event); // handle:hello
    </script>
</body>

</html>

使用ES6实现的发布订阅

http://zchange.cn/posts/332959902.html

class Event {
  constructor() {
    this._subscribers = new Map();
    this.__index = 0;
  }

  /**
   * 将订阅者信息存入list
   * @param {String} eventName 事件名称
   * @param {fn} callback 订阅回调
   * 通过Map来存取对应的订阅者
   * 监听同一个主体,下一次的不会覆盖上一次的监听
   * 返回订阅者名称,和对应的下标,可供后面销毁
   */
  subscribe(eventName, callback) {
    if (typeof eventName !== 'string' || typeof callback !== 'function') {
      throw new Error('parameter error')
    }
    if (!this._subscribers.has(eventName)) {
      this._subscribers.set(eventName,new Map());
    }
    // 订阅同一个主题通过_index不会覆盖上一次。
    this._subscribers.get(eventName).set(++this._index,callback);
    return [eventName, this._index]
  }


  on(eventName, callback) {
    return this.subscribe(eventName, callback);
  }

  /**
   * 发布信息
   * @param {String} eventName 订阅者名称
   * @param {any} args 参数
   */
  emit(eventName, ...args) {
    if(this._subscribers.has(eventName)){
      const eveMap = this._subscribers.get(eventName);
      eveMap.forEach((map) =>map(...args));
    }else{
      throw new Error(`The subscription parameter ${eventName} does not exist`)
    }

  }

  /**
   * 销毁对应订阅者
   * @param {String|Object} event 
   */
  destroy(event) {
    if (typeof event === 'string') {
      // 直接销毁对应订阅者
      if (this._subscribers.has(event)) {
        this._subscribers.delete(event)
      }
    } else if (typeof event === 'object') {
      // 通过订阅者名词和下标,销毁其中某一个订阅者
      const [eventName, key] = event;
      this._subscribers.get(eventName).delete(key);
    }
  }

  /**
   * 清除所有订阅者
   */
  remove() {
    this._subscribers.clear();
  }

}

const $event = new Event();
const ev1 = $event.on('aa', (...agrs) => {
  console.log(...agrs);
  console.log(111);
})
const ev2 = $event.on('aa', (...agrs) => {
  console.log(...agrs);
  console.log(222);
})
setTimeout(() => {
  $event.emit('aa', '1', '2');
  $event.destroy();
  $event.remove();
}, 500)

原生web api实现依赖于dom元素,即发布者和订阅者都是dom元素,因此使用场景有限。基于对象的实现方式常用于跨组件之间的数据传递,可以更好的解耦发布者和订阅者之间的联系。

posted @ 2019-09-09 13:45:50 浏览(56) 设计模式

avatar