1. 程式人生 > 其它 >再次理解訂閱釋出模式

再次理解訂閱釋出模式

之前的總結:設計模式之釋出訂閱模式

為什麼會有這種設計模式

這裡有個很好的回答: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元素,因此使用場景有限。基於物件的實現方式常用於跨元件之間的資料傳遞,可以更好的解耦釋出者和訂閱者之間的聯絡。

部落格: https://blog.86886.wang
GitHub: https://github.com/wmui