面试官:既然React/Vue能够用Event Bus进行组件通讯,你能够实现下吗?

前言

本文标题的题目是由其余问题延伸而来,面试中面试官的经常使用套路,揪住一个问题一直深挖,在产生这个问题以前必定是这个问题.html

React/Vue不一样组件之间是怎么通讯的?node

Vuegit

  1. 父子组件用Props通讯es6

  2. 非父子组件用Event Bus通讯github

  3. 若是项目够复杂,可能须要Vuex等全局状态管理库通讯面试

  4. $dispatch(已经废除)和$broadcast(已经废除)api

React数组

  1. 父子组件,父->子直接用Props,子->父用callback回调app

  2. 非父子组件,用发布订阅模式的Event模块ide

  3. 项目复杂的话用Redux、Mobx等全局状态管理管库

  4. 用新的Context Api

咱们大致上都会有以上回答,接下来极可能会问到如何实现Event(Bus),由于这个东西过重要了,几乎全部的模块通讯都是基于相似的模式,包括安卓开发中的Event Bus,Node.js中的Event模块(Node中几乎全部的模块都依赖于Event,包括不限于http、stream、buffer、fs等).

咱们仿照Node中Event API实现一个简单的Event库,他是发布订阅模式的典型应用.

提早声明: 咱们没有对传入的参数进行及时判断而规避错误,仅仅对核心方法进行了实现.


1.基本构造

1.1初始化class

咱们利用ES6的class关键字对Event进行初始化,包括Event的事件清单和监听者上限.

咱们选择了Map做为储存事件的结构,由于做为键值对的储存方式Map比通常对象更加适合,咱们操做起来也更加简洁,能够先看一下Map的基本用法与特色.

class EventEmeitter {  
     constructor() { 
    this._events = this._events || new Map(); // 储存事件/回调键值对   
    this._maxListeners = this._maxListeners || 10; // 设立监听上限   
    }
}

1.2 监听与触发

触发监听函数咱们能够用applycall两种方法,在少数参数时call的性能更好,多个参数时apply性能更好,当年Node的Event模块就在三个参数如下用call不然用apply.

固然当Node全面拥抱ES6+以后,相应的call/apply操做用Reflect新关键字重写了,可是咱们不想写的那么复杂,就作了一个简化版.

 // 触发名为type的事件
EventEmeitter.prototype.emit = function(type, ...args){  
         let handler;   // 从储存事件键值对的this._events中获取对应事件回调函数 
        handler = this._events.get(type); 
       if (args.length > 0) {   
           handler.apply(this, args);  
       }else{ 
          handler.call(this); 
       }  
       return true;
};
 // 监听名为type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
     // 将type事件以及对应的fn函数放入this._events中储存   
   if (!this._events.get(type)) {  
     this._events.set(type, fn); 
   }
};

咱们实现了触发事件的emit方法和监听事件的addListener方法,至此咱们就能够进行简单的实践了.

// 实例化 const emitter = new EventEmeitter();
// 监听一个名为arson的事件对应一个回调函数
emitter.addListener('arson', man => {  
 console.log(`expel ${man}`); 
});
 // 咱们触发arson事件,发现回调成功执行 
emitter.emit('arson', 'low-end'); // expel low-end

彷佛不错,咱们实现了基本的触发/监听,可是若是有多个监听者呢?

// 重复监听同一个事件名
 emitter.addListener('arson', man => { 
   console.log(`expel ${man}`);
}); 
emitter.addListener('arson', man => { 
  console.log(`save ${man}`); 
}); 
emitter.emit('arson', 'low-end'); // expel low-end

是的,只会触发第一个,所以咱们须要进行改造.


2.升级改造

2.1 监听/触发器升级

咱们的addListener实现方法还不够健全,在绑定第一个监听者以后,咱们就没法对后续监听者进行绑定了,所以咱们须要将后续监听者与第一个监听者函数放到一个数组里.

// 触发名为type的事件 
EventEmeitter.prototype.emit = function(type, ...args) { 
  let handler; 
  handler = this._events.get(type);  
  if (Array.isArray(handler)) { 
  // 若是是一个数组说明有多个监听者,须要依次此触发里面的函数  
      for (let i = 0; i < handler.length; i++) {  
          if (args.length > 0) {   
              handler[i].apply(this, args);  
          } else {      
                  handler[i].call(this);   
          }  
        }  
 } else {
  // 单个函数的状况咱们直接触发便可 
      if (args.length > 0) {   
               handler.apply(this, args);  
          } else {   
            handler.call(this); 
      }  
  }  
   return true;
 };
  // 监听名为type的事件
EventEmeitter.prototype.addListener = function(type, fn) { 
     const handler = this._events.get(type); 
     // 获取对应事件名称的函数清单  
      if (!handler) {   
       this._events.set(type, fn);
      } else if (handler && typeof handler === 'function') { 
         // 若是handler是函数说明只有一个监听者   
          this._events.set(type, [handler, fn]); 
          // 多个监听者咱们须要用数组储存  
      } else {  
         handler.push(fn);
          // 已经有多个监听者,那么直接往数组里push函数便可 
      }
};

是的,今后之后能够愉快的触发多个监听者的函数了.

// 监听同一个事件名 
emitter.addListener('arson', man => { 
        console.log(`expel ${man}`);
 }); 
emitter.addListener('arson', man => { 
    console.log(`save ${man}`);
 }); 
emitter.addListener('arson', man => { 
   console.log(`kill ${man}`);
 }); 
 // 触发事件 
 emitter.emit('arson', 'low-end'); //expel low-end //save low-end //kill low-end

2.2 移除监听

咱们会用removeListener函数移除监听函数,可是匿名函数是没法移除的.

EventEmeitter.prototype.removeListener = function(type, fn) {  
 const handler = this._events.get(type); // 获取对应事件名称的函数清单 
   // 若是是函数,说明只被监听了一次  
 if (handler && typeof handler === 'function') {   
   this._events.delete(type, fn);  
  }else{   
    let postion;     // 若是handler是数组,说明被监听屡次要找到对应的函数    
     for (let i = 0; i < handler.length; i++) {  
        if (handler[i] === fn) {        
           postion = i;      
        }else{   
            postion = -1;   
        }   
     }     // 若是找到匹配的函数,从数组中清除  
     if(postion !== -1){     
       // 找到数组对应的位置,直接清除此回调  
       handler.splice(postion, 1);      
        // 若是清除后只有一个函数,那么取消数组,以函数形式保存   
       if (handler.length === 1){        
           this._events.set(type, handler[0]);     
        }     
    }else{  
         return this;  
       }   
   } 
   };

3.发现问题

咱们已经基本完成了Event最重要的几个方法,也完成了升级改造,能够说一个Event的骨架是被咱们开发出来了,可是它仍然有不足和须要补充的地方.

  1. 鲁棒性不足: 咱们没有对参数进行充分的判断,没有完善的报错机制.

  2. 模拟不够充分: 除了removeAllListeners这些方法没有实现之外,例如监听时间后会触发newListener事件,咱们也没有实现,另外最开始的监听者上限咱们也没有利用到.

固然,这在面试中现场写一个Event已是很够意思了,主要是体现出来对发布-订阅模式的理解,以及针对多个监听情况下的处理,不可能现场撸几百行写一个完整Event.

索性Event库帮咱们实现了完整的特性,整个代码量有300多行,很适合阅读,你能够花十分钟的时间通读一下,见识一下完整的Event实现.

做者:寻找海蓝96连接:https://juejin.im/post/5ac2fb886fb9a028b86e328c

相关文章
相关标签/搜索