什么是事件?

事件是您在编程时系统内发生的动作或者发生的事情,系统响应事件后,如果需要,您可以某种方式对事件做出回应。浏览器和Node的事件有所不同

使用方式不同。浏览器中使用dispatchEvent 来发布事件,使用addEventListener来绑定并监听事件。Node中使用emit触发事件,使用on来监听事件。

作用对象不同。浏览器中主要为DOM事件,回掉函数中存在事件对象(event),可以理解为对外开放的钩子。Node依赖定期监听事件的监听器和定期处理事件的处理器。

事件与消息队列

事件的产生:Node.js 所有的异步 I/O 操作(net.Server, fs.readStream 等)在完成后都会添加一个事件到事件循环的事件队列中

事件的触发:事件循环会在事件队列中取出事件处理,从事件循环取出事件的时候,触发这个事件和回调函数。

消息队列:js任务都是排队执行的,每当一个任务执行之前都插入到消息队列最后,当前面没有任务时才进入主线程执行。

事件机制

浏览器中的DOM事件有事件流(事件捕获、事件冒泡),出现事件流的原因是DOM的树形结构,由于这种嵌套的结构会形成一种包含关系,事件的触发会波及其父子元素。浏览器也提供了阻止冒泡(event.stopPropagation())和阻止默认(event.preventDefault())的方法。由此也衍生出了事件委托的优化手段,事件统一处理。我们常见的DOM事件有鼠标的点击、悬浮、滑动等等,当然浏览器也提供给开发者自定义事件的能力,使用示例如下:

window.addEventListener('hello',(e)=>{
    console.log(e.detail)
  });
  window.dispatchEvent(new CustomEvent('hello', {detail:{name:'张三'}}));

Node主要是做服务端应用的框架,也提供了事件机制。其主要包含触发器和监听器,主要是EventEmitter类,基本上就是自定义事件,实现原理也就是一个发布-订阅模式。使用示例如下:

const EventEmitter = require('events');
  const myEmitter = new EventEmitter();

  myEmitter.on('hello', (res) => {
    console.log(res);
  });
  
  myEmitter.emit('hello', {name:'张三'});

js通信方式汇总

  • postMessage与iframe
  • 客户端与服务器通信(http、websocket)
  • h5与native通信 JsBridge
  • node.js进程之间通信 process.send()
  • vue和react组件间的通信方式

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

观察者模式:观察者和被观察这之间存在耦合关系,观察这直接观察被观察者,两者必须确切知道对方存在才能进行消息传递 发布-订阅模式:观察者和被观察者通过一个消息中心来代理,促成两者之间进行通信。发布者和订阅者完成的解耦,两者不需要知道对方是否存在。

观察者模式相当于我们直接去超市买东西,实时交易。 发布-订阅模式相当于我们网上买东西,我们先下单,货到了物流会给你发短信。

分别手写一个观察者和发布-订阅模式

观察者模式

/**
 * 观察者模式
 */

/**
 * 观察者
 */
function Observer(id) {
  this.id = id;
  this.update = (obs) => {
    console.log('观察者:'+this.id);
    console.log('被观察者:'+obs.id);
  }
}
/**
 * 被观察者
 */
function Observed() {
  this.observers = [];
  this.addObserver = (obs) => {
    this.observers.push(obs);
  }
  this.removeObserver = (obs) => {
    this.observers = this.observers.filter(item=>item.id!==obs.id);
  }
  this.notify = (obs) => {
    this.observers.forEach(item=>item.update(obs));
  }
}

// 测试
const observed = new Observed();

const observer1 = new Observer(1);
const observer2 = new Observer(2);
const observer3 = new Observer(3);

observed.addObserver(observer1);
observed.addObserver(observer2);
observed.addObserver(observer3);

observed.notify(observer1);

发布-订阅模式

const createEventHub = () => ({
  hub: Object.create(null),
  // 订阅
  sub(event,handle){
    if(!this.hub[event]) this.hub[event]=[];
    this.hub[event].push(handle);
  },
  //发布
  pub(event,data){
    (this.hub[event] || []).forEach(handle=>handle(data));
  },
  //取消
  off(event,handle){
    let index = this.hub[event].indexOf(handle);
    index>-1 && this.hub[event].splice(index,1);
  }
})

// 测试
const eventHub = createEventHub();
const handle = data =>{
  console.log(data);
}

eventHub.sub('myEvent',handle);
eventHub.sub('myEvent',()=>{
  console.log('handle2')
});
eventHub.sub('myEvent2',()=>{
  console.log('eventHub',eventHub);
});

eventHub.pub('myEvent','a string !!!');
eventHub.pub('myEvent',{arg:'a object!!!'});
eventHub.pub('myEvent2');

eventHub.off('myEvent',handle);

问题汇总(FAQ)

  • 浏览器跨域问题的解决方案
  • h5与native如何通信
  • node.js进程通信
  • 浏览器事件委托
  • Node Events on方法(监听函数)的调用是同步的还是异步的

参考

  • MDN文档 Event
  • MDN文档 事件介绍
  • node.js中文网 events(事件触发器)
  • JavaScript设计模式之观察者模式
  • Nodejs进程间通信
  • 源码解读,一文彻底搞懂Events模块
  • JsBridge