再次理解订阅发布模式

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

为什么会有这种设计模式

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

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

function EventEmitter() {
    this._event = {}
}

EventEmitter.prototype.on= function (type, handle) {
    this._event[type] = this._event[type] || []

    this._event[type].push(handle)
}

EventEmitter.prototype.remove = function (type, handle) {
    var index = (this._event[type] || []).indexOf(handle)

    if(index !== -1) {
        this._event[type].splice(index, 1)
    }
}

EventEmitter.prototype.emit = function (type, data) {
    (this._event[type] || []).forEach(function (handle) {
        handle(data)
    })
}

var test = new EventEmitter();

var handle1 = function (data) {
    console.log(data[0])
}

var handle2 = function () {
    console.log(data[1])
}

test.on('fetchData', handle1)

test.on('fetchData', handle2)

test.remove('fetchData', handle2)

test.emit('fetchData', [1,2,3])

// 1

常见的使用场景:当我们在ajax的异步数据请求结束后,emit一个事件,外部可以通过监听这个事件执行不同的操作

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

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

<head>
    
    
    
    <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 @ 2021-10-07 15:10  wmui  阅读(99)  评论(0编辑  收藏  举报