#36 实现一个 EventEmitter


  • 0
    administrators

    观察者模式在前端开发中非常常用,我们经常用的事件就是观察者模式的一种体现。它对我们解耦模块、开发基于消息的业务起着非常重要的作用。Node.js 原生自带 EventEmitter 模块,可见它的重要性。

    完成 EventEmitter 模块,它是一个类,它的实例具有以下几个方法:onemitoff

    • on(eventName, func):监听 eventName 事件,事件触发的时候调用 func 函数。
    • emit(eventName, arg1, arg2, arg3...):触发 eventName 事件,并且把参数 arg1, arg2, arg3... 传给事件处理函数。
    • off(eventName, func):停止监听某个事件。

    使用例子:

    const emitter = new EventEmitter()
    const sayHi = (name) => console.log(`Hello ${name}`)
    const sayHi2 = (name) => console.log(`Good night, ${name}`)
    
    emitter.on('hi', sayHi)
    emitter.on('hi', sayHi2)
    emitter.emit('hi', 'ScriptOJ')
    // => Hello ScriptOJ
    // => Good night, ScriptOJ
    
    emitter.off('hi', sayHi)
    emitter.emit('hi', 'ScriptOJ')
    // => Good night, ScriptOJ
    
    const emitter2 = new EventEmitter()
    emitter2.on('hi', (name, age) => {
      console.log(`I am ${name}, and I am ${age} years old`)
    })
    emitter2.emit('hi', 'Jerry', 12)
    // => I am Jerry, and I am 12 years old
    

    (本题来源:阿里巴巴前端笔试题)


  • 0

    这个感觉不是很简单了吧,用了半个小时,在现场面试早被催了


  • 0

    用例第三行:

    const sayHi2 = (name) => console.log('Good night, ${name}')
    

    错了哦,应该是:

    const sayHi2 = (name) => console.log(`Good night, ${name}`)
    

  • 0
    administrators

    @ME 这个应该算是比较基础的


  • 0
    administrators

    @Sunjourney 感谢指出,已改。


  • 0

    emitter.emit('hi')

    没有给 事件处理函数 添加参数


  • 0
    administrators

    @plm fixed


  • 0

    class EventEmitter {
        constructor() {
            this.events = [];
        }
    
        on(eventName, func) {
            let evt = {};
            evt[eventName] = func;
            this.events.push(evt);
        }
    
        off(eventName, func) {
            for (let i=0;i<this.events.length;i++) {
                let funcName = this.events[i][eventName].name.split(' ')[1];
                if (funcName === func.name) {
                    this.events.splice(i, 1);
                }
            }
        }
    
        emit(eventName, ...args) {
            for (let i=0;i<this.events.length;i++) {
                if (this.events[i][eventName]) {
                    if (args) {
                        this.events[i][eventName] = this.events[i][eventName].bind(null, ...args);
                    }
                    this.events[i][eventName]();
                }
            }
        }
    
    }
    

    在nodejs跑起来没问题,提交的时候发生的错误信息为: 第二次事件没有触发. 何解?


  • 0
    administrators

    @Kaedefunc.name 是不靠谱的,不同的函数可能有相同的 name


  • 0

    从react小书那边跳过来...刚入门完全不知道神马设计模式...只知道模块模式...硬着头皮写了半个多小时的一堆挫代码

    希望哈老师能帮忙批改一下 感谢

    class EventEmitter {
      constructor() {
        this.messageBox = {};
      }
    
      on(type, handle) {
        if (!this.messageBox.hasOwnProperty(type)) {
          this.messageBox[type] = [];
          handle && this.messageBox[type].push(handle);
        } else {
          handle && this.messageBox[type].push(handle);
        }
      }
    
      emit(type, ...args) {
        if (this.messageBox.hasOwnProperty(type)) {
          const handleQueen = this.messageBox[type];
          handleQueen.forEach((handle, index, arr) => {
            handle(...args);
          })
        }
      }
    
      off(type, handle) {
        if (this.messageBox.hasOwnProperty(type)) {
          const handleQueen = this.messageBox[type];
          if (handleQueen.includes(handle)) {
            const atIndex = handleQueen.indexOf(handle);
            handleQueen.splice(atIndex, 1)
          }
        }
      }
    }
    

  • 0
    administrators

    @PARADISELIN 写得很不错啊,有些地方可能可以简化一下,例如说 hasOwnProperty 来判断是没有必要的,on 里面的判断可以简化一下:

    const callbacks =  this.messageBox[type] || []
    callbacks.push(handle)
    this.messageBox[type] = callbacks
    

    这样会不会好一些?

    有些地方名字可以改一下 handleQueen 这个应该是笔误?


  • 0

    @胡子大哈 对哦 应该是handleQueue。。。谢谢哈哥 我去改一下


  • 0

    class EventEmitter {
      constructor(){
        this.fns={}
        this.cnt={}
      }
      on(str,fn){
        this.fns[str]=this.fns[str]||[]
        this.cnt[str]=this.cnt[str]||0
       fn.index=this.cnt[str]++
        this.fns[str].push(fn)
      }
      emit(str,...args){
        this.fns[str].forEach(function(fn){
          fn(...args)
        })
      }
      off(str,fn){
        this.fns[str].splice(fn.index,1)
      }
    }
    

    这么写为什么不行啊
    忘了不同事件类型的情况了,现在通过了,但是没考虑匿名函数,如果是匿名函数怎么取消事件订阅,各位大神帮忙看看


  • 1

    对设计模式一脸懵逼 参考代码后写了出来 理解后还是挺好懂的

    class EventEmitter {
      constructor() {
        this.handlers = {}
      }
      
      on(eventName, func) {
        let callbacks = eventName in this.handlers ? this.handlers[eventName] : []
        callbacks.push(func)
        this.handlers[eventName] = callbacks
      }
      
      emit(eventName, ...args) {
        if (!eventName in this.handlers) return
        const callbacks = this.handlers[eventName]
        callbacks.map(cb => {
          cb(...args)
        })
      }
      
      off(eventName, func) {
        if (!eventName in this.handlers) return
        let callbacks = this.handlers[eventName]
        let index = callbacks.indexOf(func)
        callbacks.splice(index, 1)
      }
    }
    

  • 0

    class EventEmitter {
    
        constructor() {
            this.messageBox = {};
        }
    
        on(eventName, func) {
            const callbacks = this.messageBox[eventName] || [];
            callbacks.push(func);
            this.messageBox[eventName] = callbacks;
        }
    
        emit(eventName, ...args) {
            const callbacks = this.messageBox[eventName];
            if (callbacks.length > 0) {
                callbacks.forEach((callback) => {
                    callback(...args);
                });
            }
        }
    
        off(eventName, func) {
            const callbacks = this.messageBox[eventName];
            const index = callbacks.indexOf(func);
            if (index !== -1) {
                callbacks.splice(index, 1);
            }
        }
    }
    

  • 0

    class EventEmitter{
        constructor(){
            this.events = {}
        }
    
        on(eventName,func){
            if(!this.events.hasOwnProperty(eventName)){
                this.events[eventName] = []
            }
    
            this.events[eventName].push(func)
        }
    
        emit(eventName,...rest){
            if(!this.events.hasOwnProperty(eventName)){
                return false;
            }
    
            this.events[eventName].forEach(event=>{
                event.call(this,...rest)
            })
        }
    
        off(eventName, func){
            if(!this.events.hasOwnProperty(eventName)){
                return false;
            }
    
            this.events[eventName] = this.events[eventName].filter(event=> event!==func)
        }
    }
    

  • 1

    请看看这样写如何

    class EventEmitter {
      constructor(){
        this.topics = {};
      }
      on(topic, func){
        if(!this.topics[topic]){
          this.topics[topic] = []
        }
        this.topics[topic].push({
          token: topic,
          func: func
        })
      }
      emit(...args){
        let topic = arguments[0]
        if(!this.topics[topic]){
          return false
        }
        var s = this.topics[topic],
        len = s ? s.length : 0
        while(len--){
          s[len].func(...args.slice(1))
        }
        return true
      }
      off(topic, func){
        var len = this.topics[topic].length
        for(var i=0; i<len; i++){
          if(this.topics[topic][i].func === func){
            this.topics[topic].splice(i, 1)
            return true
          }
        }
        return false
      }
    }
    
    

  • 0

    class EventEmitter {
      constructor(){
        this.list = [];
      }
      on(name, func){
        this.list.push({"name" : name, "func" : func});
      }
      emit(name, ...params){
        for(let i in this.list){
          if(this.list[i].name == name){
            this.list[i].func(...params)
          }
        }
      }
      off(name, func){
        for(let i in this.list){
          if(func === this.list[i].func){
            this.list.splice(i, 1);
          }
        }
      }
    }
    

  • 0

    class EventEmitter {
        /* TODO */
        constructor () {
            this.eventQueue = {} 
        }
      
        on(eventName, func) {
            if (!this.eventQueue[eventName]) {
                this.eventQueue[eventName] = [];
            }
            this.eventQueue[eventName].push(func); 
        }
    
        emit(eventName) {
            if (!this.eventQueue[eventName]) return;
            let args = Array.from(arguments);
            args.shift();
            this.eventQueue[eventName].forEach((func) => {
                if ( typeof func === 'function' ) {
                    func.apply(null, args);
                }
            })
        }
    
        off(eventName, func) {
            if (this.eventQueue[eventName]) {
                this.eventQueue[eventName].forEach(function(element, index) {
                    if (typeof emlement !== 'function' || typeof element === 'function' && element === func ) {
                        delete this.eventQueue[eventName][index]
                        return
                    }
                })
            }
        }
    }
    

    console 测试通过,题目页面提交提示有错,请指出问题,谢谢。


  • 0
    administrators

    @pengcc off 函数里面的那个 this.eventQueue[eventName][index] 和外层的 this 不是同一个 this


登录后回复
 

与 ScriptOJ 的连接断开,我们正在尝试重连,请耐心等待