介绍
在行为型模式中,最值得我们前端学习的设计模式就是观察者模式了,也就是我们熟悉的发布-订阅模式。因为前端与用户侧强交互的需要,我们会对用户操作实现非常多的联动依赖,当页面中某个对象状态改变后,所有依赖其状态的组件都能收到通知,并相应地改变自己的状态。而这也衍生出来一种编程思想–事件驱动编程。
在前端中,这种思想运用的非常广泛。例如 DOM 事件监听,路由变化更新,vue 的事件机制甚至是其双向绑定原理–defineProperty 等等都是基于观察者模式实现的。
基础实现
我们可以尝试实现一个观察者模式模型。这个对象至少应该包括:一个记录所有被监听事件的映射对象 subscribersMap(其中每个属性的 value 值都应为一个订阅该事件的所有函数组成的数组 subscriber)、将订阅者添加进相应事件的 subscriber 中的订阅函数、删除某个事件中订阅者的退订函数、发布者发布时广播给所有订阅者的发布函数、以及一个获取某个事件所有订阅者的查询函数组成。简单实现如下:
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
| const event = { subscribersMap: {}, subscribe: function(eventKey, func) { if (!this.subscribersMap[eventKey]) { this.subscribersMap[eventKey] = []; } this.subscribersMap[eventKey].push(func); },
unSubscribe: function(eventKey, func) { const funcList = this.subscribersMap[eventKey]; if (!funcList) { return false; } if (!func) { delete this.subscribersMap[eventKey]; } else { this.subscribersMap[eventKey] = funcList.filter( subscribeFunc => subscribeFunc !== func ); } },
publish: function(eventKey, ...args) { const funcList = this.subscribersMap[eventKey]; if (!funcList) { return false; } funcList.forEach(func => { func.apply(this, args); }); },
getSubscribeFunc: function(eventKey) { return this.subscribersMap[eventKey]; }, };
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| const subscribeFunc = answer => console.log("subscription update:", answer); event.subscribe("addNum", subscribeFunc); console.log(event.getSubscribeFunc("addNum"));
function publishFunc(a, b) { const answer = a + b; event.publish("addNum", answer); } publishFunc(1, 2);
event.unSubscribe("addNum", subscribeFunc); console.log(event.getSubscribeFunc("addNum"));
|
总结
使用观察者模式能够使得两个毫不相关的组件能够产生联动,免去了逐级传递状态信息的麻烦,并且在一对多的联动表现中优势突出。但观察者模式也有其局限性:
- 由于其影响对象之间的关系过于松散,过度使用观察者模式将导致功能的维护以及调用栈追踪变得困难。
- 订阅者一旦使用生成订阅后,该函数就会常驻内存运行,在生命周期中并没有销毁操作,过度使用将对系统造成负荷。
由此可以看出,设计模式没有银弹,我们应该正确认识到每一种设计模式带给我们的益处以及它可能为我们的项目带来的风险,在合适的地方使用合适的设计模式来解决问题。