【设计模式】前端必懂EventEmitter

3,501 阅读3分钟

本文说一下 EventEmitter,比较简单,可以直接看代码。

发布 + 订阅

DOM 的事件机制就是发布订阅模式最常见的实现,这大概是前端最常用的编程模型了,监听某事件,当该事件发生时,监听该事件的监听函数被调用。

发布订阅模式,阮一峰在《Javascript 异步编程的 4 种方法》,中:

我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。

感兴趣的可以去看看,下图是我平时在用的一个实现。(重点看)

class EventEmitter {
  constructor() {
    this._events = Object.create(null);
  }

  on(type, handler) {
    (this._events[type] || (this._events[type] = [])).push(handler);
  }

  off(type, handler) {
    if (this._events[type]) {
      this._events[type].splice(this._events[type].indexOf(handler) >>> 0, 1);
    }
  }

  once(type, handler) {
    let fired = false;

    function magic() {
      this.off(type, magic);

      if (!fired) {
        fired = true;

        handler.apply(this, arguments);
      }
    }

    this.on(type, magic);
  }

  emit(type) {
    let payload = [].slice.call(arguments, 1);

    let array = this._events[type] || [];
    for (let i = 0; i < array.length; i++) {
      let handler = this._events[type][i];
      handler.apply(this, payload);
    }
  }
}

export default EventEmitter;

Vue 中非父子组件组件通信

在 Vue 中不同组件之间通讯,有一种解决方案叫Event Bus,这其实就是发布订阅模式的实现,非常简单好用。

// bus.js
export default new Vue();

// 使用$on监听
import Bus from '../bus.js';

export default {
  created(){
    Bus.$on('fulfilled',text=>{
      this.status = 'fulfilled'
    })
  }
}

// 使用$emit触发事件
fetch('/data.json).then((res)=>{
  return res.json()
}).then(()=>{
  Bus.$emit('fulfilled','已成功')
}).catch(()=>{
  Bus.$emit('resolved','出错');
})

Vue 事件相关接口:
on,emit,once,off

使用 Event Bus 进行跨组件消息传递很棒,但是,当事件非常多的时候,Event Bus 上的事件就会难以维护,这与使用全局变量一样一个道理。变量能局部的,就尽量不要放到全局,同样 Event Bus 上的事件也是越少越好。

观察者模式

简单创建一个能够添加、删除和提醒观察者的目标。

class Subject {
  constructor() {
    this.observer_list = [];
  }

  add_observer(obj) {
    this.observer_list.push(obj);
  }

  remove_observer(obj) {
    for (let i = 0; i < this.observer_list.length; i++) {
      if (this.observer_list[i] === obj) {
        this.observer_list.splice(i, 1);
      }
    }
  }

  notify() {
    let args = Array.prototype.slice.call(arguments, 0);
    for (let i = 0; i < this.observer_list.length; i++) {
      this.observer_list[i].update(args);
    }
  }
}

代码会说话,我就不解释了,实在是没什么好解释的,如果一定要说点什么:我真有点不好意思打原创的标。

发布订阅模式 与 观察者模式

有时候人们会简单的把发布订阅模式和观察者模式混为一谈,比如上面的一峰老师,其实两者还是有些区别的。至少它们的英文名称不同:Observer vs Pub-Sub

《Learning JavaScript Design Patterns》一书这样说:

“While the Observer pattern is useful to be aware of, quite often in the JavaScript world, we’ll find it commonly implemented using a variation known as the Publish/Subscribe pattern.” 虽然 Observer 模式非常有用,但是在 JavaScript 的世界中,它更多的以一种被称为发布/订阅模式的变种来实现

由上可以理解为,发布/订阅模式是观察者模式的一种变形,两者区别在于,发布/订阅模式在观察者模式的基础上,在目标和观察者之间增加一个调度中心。

观察者模式是由具体目标调度,比如当事件触发,Subject 就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的。

发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。

详细内容,感兴趣的同学可以看下面这本书: