美文网首页
实现一个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类

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