设计模式的 js 实现 (3)--观察者模式
提示
了解设计模式是学习一切软件架构设计的基础,大到一个项目的整体框架设计,小到一个功能函数的优化,都有着重要意义。《代码大全》中将设计模式共分为了 23 类,分别为:
- 创建型模式(5 种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
- 结构型模式(7 种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式(11 种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
接下来我将针对其中常用的几种设计模式进行解读与实现,供大家参考。
介绍
在行为型模式中,最值得我们前端学习的设计模式就是观察者模式了,也就是我们熟悉的发布-订阅模式。因为前端与用户侧强交互的需要,我们会对用户操作实现非常多的联动依赖,当页面中某个对象状态改变后,所有依赖其状态的组件都能收到通知,并相应地改变自己的状态。而这也衍生出来一种编程思想--事件驱动编程。
在前端中,这种思想运用的非常广泛。例如 DOM 事件监听,路由变化更新,vue 的事件机制甚至是其双向绑定原理--defineProperty 等等都是基于观察者模式实现的。
基础实现
我们可以尝试实现一个观察者模式模型。这个对象至少应该包括:一个记录所有被监听事件的映射对象 subscribersMap(其中每个属性的 value 值都应为一个订阅该事件的所有函数组成的数组 subscriber)、将订阅者添加进相应事件的 subscriber 中的订阅函数、删除某个事件中订阅者的退订函数、发布者发布时广播给所有订阅者的发布函数、以及一个获取某个事件所有订阅者的查询函数组成。简单实现如下:
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];
},
};
const subscribeFunc = answer => console.log("subscription update:", answer);
event.subscribe("addNum", subscribeFunc);
console.log(event.getSubscribeFunc("addNum")); // [ [Function: subscribeFunc] ]
// 在不相关的组件内
function publishFunc(a, b) {
const answer = a + b;
event.publish("addNum", answer);
}
publishFunc(1, 2); // subscription update: 3
event.unSubscribe("addNum", subscribeFunc);
console.log(event.getSubscribeFunc("addNum")); // []
总结
使用观察者模式能够使得两个毫不相关的组件能够产生联动,免去了逐级传递状态信息的麻烦,并且在一对多的联动表现中优势突出。但观察者模式也有其局限性:
- 由于其影响对象之间的关系过于松散,过度使用观察者模式将导致功能的维护以及调用栈追踪变得困难。
- 订阅者一旦使用生成订阅后,该函数就会常驻内存运行,在生命周期中并没有销毁操作,过度使用将对系统造成负荷。
由此可以看出,设计模式没有银弹,我们应该正确认识到每一种设计模式带给我们的益处以及它可能为我们的项目带来的风险,在合适的地方使用合适的设计模式来解决问题。
Powered by Waline v2.15.8