美文网首页
实现一个EventEmitter类

实现一个EventEmitter类

作者: 一蓑烟雨任平生_cui | 来源:发表于2021-01-25 13:26 被阅读0次

EventEmitter是Node.js的内置模块events提供的一个类,它是Node事件流的核心。

下面模拟实现一个Node中的EventEmitter。具体包括以下方法:

方法名 方法描述
addListener(event, listener) 为指定事件添加一个监听器到监听器数组的尾部。
prependListener(event,listener) 与addListener相对,为指定事件添加一个监听器到监听器数组的头部。
on(event, listener) 其实就是addListener的别名
once(event, listener) 为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器。
prependOnceListener(event, listener) 添加单次监听器 listener 到名为 eventName 的事件的监听器数组的开头。
removeListener(event, listener) 移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器
off(event, listener) removeListener的别名
removeAllListeners([event]) 移除所有事件的所有监听器, 如果指定事件,则移除指定事件的所有监听器。
setMaxListeners(n) 默认情况下, EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息。 setMaxListeners 函数用于提高监听器的默认限制的数量。
listeners(event) 返回指定事件的监听器数组。
emit(event[, ...rest]) 按参数的顺序执行每个监听器,如果事件有注册监听返回 true,否则返回 false。

以下是第一个版本,维护events和onceEvents的目的是,通过once订阅的事件,监听器在内部封装了一层,所以移除时传入的监听器不是实际订阅的监听器。一开始没想到好的方法,所以维护了两个对象。可以下翻直接看第二版。


  class EventEmitter {
    constructor() {
      this._events = Object.create(null)
      this._onceEvents = Object.create(null)
      this._maxListeners = 10
    }

    _perform(events, rest) {
      events.forEach(fn => fn.apply(this, rest))

      const { _maxListeners } = this

      if (!(_maxListeners !== 0 || _maxListeners !== Infinity)) {
        console.log(55765)
        if (events.length > _maxListeners) {
          console.error(
            'MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit'
          )
        }
      }

      return true
    }

    emit(event, ...rest) {
      const events = this._events[event]
      const onceEvents = this._onceEvents[event]

      if (events) {
        return this._perform(events, rest)
      }
      if (onceEvents) {
        delete this._onceEvents[event]

        return this._perform(onceEvents, rest)
      }

      return false
    }

    _bind(type, event, listener, method = 'push') {
      const events = this[type][event]

      this[type][event]?.[method](listener) || (this[type][event] = [listener])

      return this
    }

    addListener(event, listener) {
      return this._bind('_events', event, listener)
    }

    prependListener(event, listener) {
      return this._bind('_events', event, listener, 'unshift')
    }

    on(event, listener) {
      return this.addListener(event, listener)
    }

    off(event, listener) {
      return this.removeListener(event, listener)
    }

    prependOnceListener(event, listener) {
      return this._bind('_onceEvents', event, fn, 'unshift')
    }

    once(event, listener) {
      return this._bind('_onceEvents', event, listener)
    }

    _remove(type, event, listener) {
      const events = this[type][event]

      if (events) {
        this[type][event] = events.filter(fn => fn !== listener)
      }

      return this
    }

    removeListener(event, listener) {
      let type = '_events'

      if (this._onceEvents[event]) {
        type = '_onceEvents'
      }

      return this._remove(type, event, listener)
    }

    removeAllListeners(event) {
      if (this._events[event]) {
        delete this._events[event]
      } else if (this._onceEvents[event]) {
        delete this._onceEvents[event]
      }

      return this
    }

    listeners(event) {
      return this._events[event] || this._onceEvents[event]
    }

    setMaxListeners(limit) {
      this._maxListeners = limit
    }
  }

以下是最终版,对于once订阅的事件,在内部创建了一个函数,并且将监听器挂载在函数上,在移除时会进一步判断监听器是否有fn属性。这样只需要维护一个events即可。


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

    _bind(event, listener, method = 'push') {
      ;(this._events[event] || (this._events[event] = []))[method](listener)
      return this
    }

    addListener(event, listener) {
      return this._bind(event, listener)
    }

    on(event, listener) {
      return this.addListener(event, listener)
    }

    prependListener(event, listener) {
      this._bind(event, listener, 'unshift')
    }

    _once(event, listener, method = 'push') {
      const foo = (...rest) => {
        this.removeListener(event, foo)
        listener.apply(this, rest)
      }

      foo.fn = listener

      return this._bind(event, foo, method)
    }

    once(event, listener) {
      return this._once(event, listener, 'push')
    }

    prependOnceListener(event, listener) {
      return this._once(event, listener, 'unshift')
    }

    removeListener(event, listener) {
      if (!arguments.length) {
        this._events = Object.create(null)

        return this
      }

      const cbs = this._events[event]

      if (!cbs) return this
      if (!listener) {
        this._events[event] = null
        return this
      }

      // 这种方式有个缺陷是,假设在多个 on 之间 使用once 监听了一次,那么在off中移除监听器时会导致索引错误
      // let cb
      // let i = cbs.length
      // while (i--) {
      //   cb = cbs[i]
      //   if (cb === listener || cb.fn === listener) {
      //     cbs.splice(i, 1)
      //     break
      //   }
      // }

      // OR
      this._events[event] = cbs.filter(cb => {
       // if (cb === listener || cb.fn === listener) return false
        // return true
        
        // OR
        return !(cb === listener || cb.fn === listener)
      })

      return this
    }

    off(event, listener) {
      return this.removeListener(event, listener)
    }

    emit(event, ...args) {
      const cbs = this._events[event]

      if (cbs) {
        const len = cbs.length
        const { _maxListeners } = this
        cbs.forEach(cb => {
          cb.apply(this, args)
        })

        if (!(_maxListeners !== 0 || _maxListeners !== Infinity)) {
          if (len > _maxListeners) {
            console.error(
              'MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit'
            )
          }
        }
      }

      return this
    }

    listeners(event) {
      return this._events[event]
    }

    setMaxListeners(limit) {
      this._maxListeners = limit
    }
  }

对于once订阅的监听器,通过将其挂载在封装的函数上,不仅可以同时通过on 和 once订阅同名事件,而且在emit完之后,移除once订阅的监听器,这样就不需要维护两个对象,相比较方式一在emit时还要合并两个事件的监听器去执行,完成之后单独移除once监听的事件,方式二简直完美。

相关文章

  • EventEmitter class

    在Node中,要实现观察者模式非常的简单,而且内置于EventEmitter类中,EventEmitter类允许我...

  • 实现一个EventEmitter类

    EventEmitter是Node.js的内置模块events提供的一个类,它是Node事件流的核心。 下面模拟实...

  • nodejs的EventEmitter类

    EventEmitter 类events 模块只提供了一个对象: events.EventEmitter。Even...

  • Node.js EventEmitter

    EventEmitter 类 events 模块只提供了一个对象: events.EventEmitter。Eve...

  • emit,EventEmitter,subscribe,next

    1 EventEmitter 介绍 EventEmitter 是封装的Observable类 export cla...

  • Node Native (1) event

    所有能触发事件的对象都是 EventEmitter 类的实例。 这些对象开放了一个 eventEmitter.on...

  • 面试题

    实现一个promise 实现一个EventEmitter 观察者模式

  • 第五节: 内置模块(一):事件模块events

    1.EventEmitter 类 用于实现各类事件的event模块,所有可能触发事件的对象都是一个继承了Event...

  • EventEmitter简单实现

    EventEmitter是什么 EventEmitter本质上是一个观察者模式的实现,所谓观察者模式。 正常包含两...

  • angular6.x--其它

    EventEmitter实现自定义事件 通常,指令使用 Angular EventEmitter 来触发自定义事件...

网友评论

      本文标题:实现一个EventEmitter类

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