发布-订阅模式在开发中的应用实际上是很普遍的,好比你们都知道的 Vue,使用数据驱动模板的改变,将咱们的双手从繁琐的 dom 操做中解放出来,稍微懂一些原理的同窗们都知道,其双向数据绑定就是经过数据劫持、发布-订阅和 dom 模板编译实现的,就算不了解这个的同窗,js 中的事件监听相信作前端开发的同窗都写过;前端
其实事件监听就是一个订阅的操做,当某个事件触发的时候,对该事件监听时传入的 callback 就会被执行,这就是一个完整的发布的-订阅流程;接下来咱们就来简单的写一下一个发布订阅器:数组
俗话说:磨刀不误砍柴工,在动手以前,缕清一下思路是有必要的,首先咱们要明确这个发布订阅器的功能,其中包括bash
基本功能:app
升级功能:dom
接下来咱们就分两版上代码:函数
/** * 发布订阅器构造函数 */ var Publisher = (function() { function Publisher() { this._subs = {}; // 维护一个订阅器列表 } /** * 添加订阅者 * 若订阅者须要插入的订阅器不存在,则新建立一个 * @param { string } type - 须要添加订阅者的订阅器名 * @param { function } func - 订阅者 */ Publisher.prototype.addSub = function(type, func) { if(!this._subs[type]) this._subs[type] = []; this._subs[type].push(func); }; /** * 发布通知 * 通知指定订阅器执行其中的每一个订阅者 * @param { string } type - 须要通知其发布消息的订阅器名 */ Publisher.prototype.notify = function(type) { if(!this._subs[type]) return; var args = Array.prototype.slice.call(arguments, 1); this._subs[type].forEach(function(item) { item.apply(this, args); }, this); }; /** * 删除订阅者 * @param { string } type - 指定操做的订阅器名 * @param { function } func - 指定须要删除的订阅者 */ Publisher.prototype.destory = function(type, func) { this._subs[type].forEach(function(item, index, array) { (item === func) && array.splice(index, 1); }, this); }; return Publisher; }());
基础版就如上面的代码,以最简单的方式实现一个发布订阅器的基本功能(能订阅,并能接收消息,还能指定删除)this
/** * 发布订阅器构造函数 */ var Publisher = (function() { function Publisher() { this._subs = {}; } /** * 添加订阅者 * 若订阅者须要插入的订阅器不存在,则新建立一个 * 若传入的订阅器名为一个数组,则遍历数组内的每一个订阅器添加订阅者 * @param { string|Array<string> } type - 须要添加订阅者的订阅器名 * @param { function } func - 订阅者 * @returns { object } - 发布器实例,可实现链式调用 */ Publisher.prototype.addSub = function(type, func) { if(Array.isArray(type)) { type.forEach(function(item) { this.addSub(item, func); }, this); } else { (this._subs[type] || (this._subs[type] = [])).push(func); } return this; }; /** * 添加单次订阅 * 当该订阅者在被通知执行一次以后会从订阅器中移除 * @param { string|Array<string> } type - 须要添加订阅者的订阅器名 * @param { function } func - 订阅者 * @returns { object } - 发布器实例,可实现链式调用 */ Publisher.prototype.once = function(type, func) { function onceAdd() { var args = Array.prototype.slice(arguments); this.destory(type, onceAdd); func.apply(this, args); } this.addSub(type, onceAdd); return this; }; /** * 发布通知 * 通知指定订阅器执行其中的每一个订阅者 * @param { string } type - 须要通知其发布消息的订阅器名 * @returns { object } - 发布器实例,可实现链式调用 */ Publisher.prototype.notify = function(type) { if(this._subs[type] === void 0) throw TypeError("Can't find the " + type + " event in the Publisher"); var args = Array.prototype.slice.call(arguments, 1); this._subs[type].forEach(function(item) { item.apply(this, args); }, this); return this; }; /** * 删除订阅器/订阅者 * 若没有传入参数直接调用方法,将清空整个订阅器列表 * 若只传入了订阅器类型参数,没有指定删除的订阅者,则删除该订阅器 * @param { string|Array<string> } type - 指定操做的订阅器名,可用数组传入多个须要操做的订阅器名 * @param { function } func - 指定须要删除的订阅者 * @return { object } - 发布器实例,可实现链式调用 */ Publisher.prototype.destory = function(type, func) { if(func && typeof func !== 'function') throw TypeError('The param "func" should be a function!'); if(!arguments.length) { this._subs = {}; return this; } if(Array.isArray(type)) { type.forEach(function(item) { this.destory(item, func); }, this); } if(!func) { delete this._subs[type]; } else { this._subs[type].forEach(function(item, index, array) { (item === func) && array.splice(index, 1); }, this); } return this; }; return Publisher; }());
以上就是一个简单的发布订阅器了,如有什么不足的地方,还望各位同窗指正!spa