又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
在js中,有一种很简单发布订阅模式,也就是DOM事件,addEventListener
就可以实现订阅dom节点上的事件,触发事件后就执行回调
1. 自定义事件
实现发布订阅的步骤
- 指定好发布者
- 为发布者添加一个缓存列表,用以存放回调函数以通知订阅者
- 发布消息的时候,发布者会遍历这个缓存列表,依次触发订阅者回调函数。
下面用代码实现一下发布-订阅模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| interface Observers { [event: string]: Array<Function> }
let publisher = { observers: <Observers>{}, add(event, fn) { if (!this.observers[event]) { this.observers[event] = []; } this.observers[event].push(fn); }, remove(event, fn) { let fns = this.observers[event];
if (!fns) { return false; } if (!fn) { fns && (fns.length = 0); } else { for (let l = fns.length - 1; l >= 0; l--) { if (fns[l] === fn) { fns.splice(l, 1); } } } }, trigger(event, ...args) { let fns = this.observers[event];
if (!fns || fns.length === 0) { return false; }
for (let i = 0, fn; fn = fns[i++];) { fn.apply(this, args); } }, }
|
如果要实现一个通用的发布-订阅模式
1 2 3 4 5
| let installEvent = function(obj) { for (let i in publisher) { obj[i] = publisher[i]; } }
|
接下来我们来尝试使用一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let salesOffices: any = {}; installEvent(salesOffices);
salesOffices.add('squareMeter88', function(price) { console.log('小美') console.log('价格=' + price); })
salesOffices.add('squareMeter100', function (price) { console.log('小明') console.log('价格=' + price); })
salesOffices.trigger('squareMeter88', 20000); salesOffices.trigger('squareMeter100', 30000);
|
但是这样salesOffices
对象依旧具有耦合度,订阅者必须知道发布者的名字,才能订阅到事件,可以使用全局的发布者Event对象进行发布,就像vue中的eventBus
,这样做虽然减少了耦合度,但是消息来自哪个模块,以及会流向哪个模块都是不直观的,容易带来维护性上的问题,并且通过全局的来订阅发布消息,容易造成命名冲突。
2.发布-订阅模式的两种模型
- 推模型:当一个事件发生后将通知所有订阅者,并把所有的新数据传给他们
- 拉模型:当一个事件发生后将通知所有订阅者,每个订阅者将拉取自己需要的数据
- 可以让订阅者按需获取
- 但有可能让发布者变成一个“门户大开”的对象,同时增加代码量和复杂度
参考
《javascript设计模式与开发实践》