前端基础走查(五):事件和通信以及发布订阅模式
什么是事件?
事件是您在编程时系统内发生的动作或者发生的事情,系统响应事件后,如果需要,您可以某种方式对事件做出回应。浏览器和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方法(监听函数)的调用是同步的还是异步的