JavaScript设计模式系列--发布订阅模式
栏目: JavaScript · 发布时间: 5年前
内容简介:发布订阅模式是JavaScript设计模式系列中什么是设计模式?什么是设计模式系列中的发布订阅模式?
发布订阅模式是JavaScript设计模式系列中 特别重要的一种,特别重要,特别重要 ···
思考一下
什么是设计模式?
- 设计模式是 前人总结的用于解决开发过程中某类问题的方法 。
什么是 设计模式 系列中的发布订阅模式?
往下看···
理解“发布”和“订阅”
有些文章中介绍发布订阅模式是喜欢用一些案例来描述,这些案例很容易理解,但是把这些案例转换到程序描述的过程中很容易产生晕晕的感觉。那么在这篇文章中我们从概念的角度开始逐步分析“发布订阅模式”。
什么是“发布”?
“发布”,一般是指发布信息。在现实社会中怎么发布信息呢?比如用你们村里的大喇叭广播你马上要结婚了,让全村的人都知道。 你也可以写N封婚礼邀请函,直接交给N个你想让他们参加你婚礼的人,其他未收到邀请函的人就不知道你举办婚礼的事。发布信息的方式有很多很多 ···
什么是“订阅”?
“订阅”,预订阅览,一般是指订阅某种东西,比如你订阅了一份新华日报,那么每天某个固定时间点你就能收到邮递员给你送来的报纸。换方式去理解就是你想定时收取某种信息,那么信息就会在那个时间点送到你身边。
我们人类这么聪明当然会想到各种方法去发布和订阅自己的信息,那么 计算机程序该怎么“发布”和“订阅”它的信息呢?
下面我们以JavaScript程序为例,模拟现实社会中的“发布订阅”信息的过程。那么这个时候就要用面向对象的思想来分析这个过程。首先我们线对“发布订阅”这个过程进行程序建模。
// 发布订阅模型 var Publisher = { watchers: { // 已经订阅的事件, 每个事件类型的值是一个数组,用来存放该事件下需要触发的所有回调函数 'tpye1': [cb1, cb2 ...], 'type1': [cb1, cb2 ...], }, // addWatcher: function(type, cb) { //添加订阅者,订阅者其实就是添加了相应的事件及其被触发时对应的回调函数 // type 订阅类型 // cb 回调函数 // 这里将相应的事件type以及其对应的cb存入到this.wathers对象里面 }, removeWatcher: function(type, cb) { // 删除订阅者 // type 订阅类型 // cb 回调函数 // 这里将相应的事件type以及其对应的cb从this.wathers对象里面删除 }, on: function() { // 监听,然后对所有订阅了该type的订阅者发布消息,发布消息其实就是触发对应的回调函数 // 此处可以使用arguments属性获取其参数 // 这里要触发对应type的回调函数 } } 复制代码
为了理解起来方便,上面程序仅仅是建立的发布订阅模型,是不是很简单?下面我们用Js来完善这个模型使其能够工作。
// 发布者类 class Publisher { constructor() { this.watchers = {}; } //添加订阅者,订阅者其实就是添加了相应的事件及其被触发时对应的回调函数 addWatcher(type, cb) { if(!this.watchers[type]) { this.watchers[type] = [] } this.watchers[type].push(cb); } // 删除订阅者 removeWatcher(type, cb) { var cbs = this.watchers[type]; // 取出该类型对应的消息集合 if(!cbs) { return false; } if(!cb) { cbs && (cbs.length = 0); }else { for(var i=0; i<cbs.length; i++) { if(cb === cbs[i]) { cbs.splice(i, 1); } } } } // 监听,有点程序中会用trigger名,然后对所有订阅了该type的订阅者发布消息,这个过程根据type就是触发对应的回调函数 on() { var type = [].shift.call(arguments); var cbs = this.watchers[type]; if(!cbs || cbs.length == 0) { return false; } for(var i=0; i<cbs.length; i++) { cbs[i].apply(this, arguments); } } } // 发布者实体对象 var publishObj = new Publisher(); // 添加订阅type为'console' publishObj.addWatcher('console', function() { var msg = [].shift.call(arguments); console.log(msg); }); // 添加订阅type为'alert' publishObj.addWatcher('alert', function() { var msg = [].shift.call(arguments); alert(msg); }); publishObj.on('console', '触发console 1!'); publishObj.on('console', '触发console 2!'); publishObj.on('alert', '触发alert!'); publishObj.removeWatcher('alert', cb2); // 注意这里是按照地址引用的。如果传入匿名函数则删除不了 publishObj.on('alert', '触发alert!'); 复制代码
上面是发布订阅模式的基本程序案例,基于这个案例我们可以拓展出很多常见的应用,请看下文。
发布订阅模式应用案例
Vue - EventBus
《面试官:既然React/Vue可以用Event Bus进行组件通信,你可以实现下吗?》
这篇文章中作者由浅入深的介绍了实现EventBus的思路,并给出了相应JavaScript实现程序。我们仔细分析代码就能发现EventBus的实现就是基于发布订阅模式。下面我们引用一下该文章中的程序,简单做了下改造(将 prototype
上方法的实现直接放从class内部)。
提前声明:我们没有对传入的参数进行及时判断而规避错误,仅仅对核心方法进行了实现。
class EventEmeitter { constructor() { this.events = this.events || new Map(); this.maxListeners = this.maxListeners || 10; } // 监听,然后对所有订阅了该type的订阅者发布消息,与上面不同的是这里的type后面为参数非回调函数 emit(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); } } } // 添加订阅事件类型 addListener(type, callback) { const handler = this.emit.get(type); if(!handler) { this.events.set(type, callback) }else if(handler && typeof handler === 'function') { this.events.set(type, [handler, callback]); }else { handler.push(callback); } } // 删除订阅事件类型 removeListener(type, callback) { var handler = this.events.get(type); if(handler && typeof handler === 'function') { this.events.delete(type, callback); }else { let position; for(let i=0; i<handler.length; i++) { if(handler[i] === callback) { position = i; }else { position = -1; } } if(position !== -1) { handler.splice(i, 1); if(handler.length == 1) { this.events.set(type, handler[0]) } }else { return this; } } } } 复制代码
EventBus实现的过程基本和上文中介绍的发布订阅模式思路一致,仅仅是具体业务处理逻辑不同。
Vue - 双向绑定/数据劫持--发布订阅
先看一下Vue实现双向数据绑定的程序,其主要思想是observer每个对象的属性,添加到订阅器dep中,当数据发生变化的时候发出notice通知。 相关源代码(为方便阅读已经去掉flow部分)如下:(作者采用的是ES6+flow写的,代码在src/core/observer/index.js模块里面)。
export function defineReactive(obj, key, val, customSetter, shallow) { const dep = new Dep(); // 创建订阅对象 const property = Object.getOwnPropertyDescriptor(obj, key); // 获取当前对象自身属性描述,返回值为对象(有多个描述属性),原型上属性无法获取 if(property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val); // 创建一个观察者对象 Object.defineProperty(obj, key, { enumerable: true, configurale: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val if(Dep.target) { dep.depend() if(childOb) { childOb.dep.depend() if(Array.isArray(value)) { dependArray(value) } } } return value; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val if(newVal === value || (newVal !== newVal && value !== value)) { return } if(ProcessingInstruction.env.NODE_ENV !== 'production' && customSetter) { customSetter(); } if(setter) { setter.call(obj, newVal) }else { val = newVal } childOb = !shallow && observe(newVal) // 继续监听新的属性值 dep.notify() // 这个是真正劫持的目的,要对订阅者发布通知了 } }); } 复制代码
上面程序是双向数据绑定/数据劫持的部分,下面我们看一下订阅者对象也就是 Dep
的实现源码。
export default class Dep { constructor() { this.id = uid++; this.subs = [] } // 添加订阅 addSub(sub) { this.subs.push(sub); } // 删除订阅 removeSub(sub) { remove(this.subs, sub) } // depend() { if(Dep.target) { Dep.target.addDep(this) } } // 发布消息 notify() { const subs = this.subs.slice(); if(ProcessingInstruction.env.NODE_ENV !== 'production' && !config.async) { subs.sort((a, b) => a.id - b.id) } for(let i=0, l=subs.length; i<l; i++) { subs[i].update() } } } 复制代码
vue中发布订阅模式应用在上文中做了简单介绍,后续会有文章专门介绍vue原理。
总结
发布订阅模式的核心过程其实分为两步,一是添加订阅也就是添加监听事件及对应的方法,二是发布消息也就是根据事件类型触发相应的方法。
so,你可以根据发布订阅模式的原理联想到更多的实际业务问题吗?
参考文章
以上所述就是小编给大家介绍的《JavaScript设计模式系列--发布订阅模式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 设计模式——订阅模式(观察者模式)
- 设计模式-简单工厂、工厂方法模式、抽象工厂模式
- java23种设计模式-门面模式(外观模式)
- 设计模式-享元设计模式
- Java 设计模式之工厂方法模式与抽象工厂模式
- JAVA设计模式之模板方法模式和建造者模式
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTML 压缩/解压工具
在线压缩/解压 HTML 代码
Markdown 在线编辑器
Markdown 在线编辑器