发布-订阅设计模式对你们来讲并非很陌生,举一个最简单的例子,在前端开发过程当中,事件的绑定就是其实际的应用。首先咱们先了解下什么是发布-订阅模式。前端
基本概念:发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,全部依赖它的对象都获得通知。在前端开发中,咱们通常用事件模型来替代传统的发布-订阅模式。编程
发布-订阅模式是前端经常使用的一种设计模式,如今主流的MVVM框架,都大量使用了此设计模式,其主要做用有如下两点:一是能够实现模块间通讯,而是能够在必定程度上实现异步编程。其基本事件模型以下:设计模式
前端的事件绑定有三要素,一是传入事件类型,二是声明对应的回调方法,三是触发条件;触发条件为对应的事件类型。前端DOM的事件系统本质也是发布-订阅模式,而咱们在业务处理中所应有的模式也与此相似,只不过发布订阅模式应用的是自定义事件类型,能够自定义。缓存
发布订阅事件模型本质上就是一个中央事件处理总线,它接收全部订阅的自定义事件,并将自定义事件的回调存储到事件回调的堆栈中,当某在某个时刻触发了此自定义事件,会调用分发事件的方法,从事件回调堆栈中取出符合条件的事件回调,依次调用,具体实现逻辑以下:app
class EventEmiter { constructor() { //回调中心 this.listenerList = {}; this.createEventList = {}; } /** * 添加事件监听 * @param {事件类型} type * @param {回调方法} fn */ on(type, fn) { if (!this.listenerList[type]) { this.listenerList[type] = []; } this.listenerList[type].push(fn); } /** * 触发事件监听 * @param {事件类型} type */ emit(type, flag) { let fnList = this.listenerList[type]; let params = Array.from(arguments); if (!fnList || fnList.length === 0) { return false; } else { fnList.forEach(fn => { fn.apply(this, params.slice(1)); }); } } /** * 移除事件监听 * @param {事件类型} type * @param {回调方法} fn */ off(type, fn) { if (!this.listenerList[type]) { return false; } else { let index = this.listenerList[type].findIndex(vv => vv === fn); if (index === -1) { return false; } else { this.listenerList[type].splice(index, 1); } } } } let eventBus = new EventEmiter(); function cb(param) { console.log("this is a test", param); } eventBus.on("test", cb); eventBus.emit("test", 123); eventBus.off("test", cb); eventBus.emit("test", 456);
以上只是对发布订阅模式进行一个简单的实现,自定义事件只能分发给在触发前已订阅的消息,针对那些先触发,后订阅的内容,并不能获得一个很好的处理,因此,若是要解决这种弊端,就必须加一个离线的事件缓存。除此以外,发布订阅也有一些弊端,那就是每次发布消息,都会触发全部的事件监听回调,尽管大多数状况下并不想触发全部回调内容,因此在这种状况下,最好对事件加一些命名空间,以缩小其生效范围。框架
如下为支持离线事件代码,只是对事件加了一个标记:异步
class EventEmitter { constructor() { //回调中心 this.listenerMap = {}; //离线事件列表 this.offlineListenerList = []; } /** * 添加事件监听 * @param type 事件类型 * @param fn 回调函数 * @param flag 是不是离线事件 */ on(type, fn, flag) { if (!this.listenerMap[type]) { this.listenerMap[type] = []; } this.listenerMap[type].push(fn); //若是注册了离线事件,则在监听事件时,须要检测是否有离线事件缓存 if (flag) { let index = this.offlineListenerList.findIndex(vv => vv.type === type); if (index !== -1) { fn.call(this, this.offlineListenerList[index].params); //清空该条离线事件记录 this.offlineListenerList.splice(index, 1); } } } /** * 触发事件监听 * @param type 事件类型 * @param params 载荷参数 * @param flag */ emit(type, params, flag) { let fnList = this.listenerMap[type]; if (fnList && Array.isArray(fnList)) { fnList.forEach(fn => { fn.apply(this, params); }); } //若是注册的是离线事件,则吧 if (flag) { this.offlineListenerList.push({ type, params }); } } /** * 移除事件监听 */ off(type, fn) { if (!this.listenerMap[type]) { return false; } else { let index = this.listenerMap[type].findIndex(vv => vv === fn); if (index === -1) { return false; } else { this.listenerMap[type].splice(index, 1); } } } /** * 只触发一次 * @param type * @param fn */ once(type, fn) { let fnList = this.listenerMap[type]; let params = Array.from(arguments); if (!fnList || fnList.length === 0) { return false; } else { let index = fnList.findIndex( vv => vv === fn); fnList[index].apply(this, params.slice(1)); fnList.splice(index, 1); } } } let event = new EventEmitter(); event.emit('test', 1, true); event.on('test', params => { console.log('offline', params) }, true); event.on('cc', () => { console.log('normal', 22222); }); event.emit('cc');