JavaScript设计模式整理
一些常见模式实现:点我
什么是设计模式?
一个模式就是一个可重用的方案,可应用于在软件设计中的常见问题。另一种解释就是一个我们如何解决问题的模板 - 那些可以在许多不同的情况里使用的模板。
设计模式的分类:
创建型设计模式: 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、Widget模式 36、MVC模式 37、MVP模式 38、MVVM模式
备注:该分类借鉴于《JavaScript设计模式-张容铭》
观察者模式
所谓观察者模式,其实就是为了实现松耦合(loosely coupled)。
观察者模式指的是一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进行更新。
在观察者模式中,Subject 对象拥有添加、删除和通知一系列 Observer 的方法等等,而 Observer 对象拥有更新方法等等。
在 Subject 对象添加了一系列 Observer 对象之后,Subject 对象则维持着这一系列 Observer 对象,当有关状态发生变更时 Subject 对象则会通知这一系列 Observer 对象进行更新。
用《Head First设计模式》里的气象站为例子,每当气象测量数据有更新,
changed()
方法就会被调用,于是我们可以在changed()
方法里面,更新气象仪器上的数据,比如温度、气压等等。但是这样写有个问题,就是如果以后我们想在
changed()
方法被调用时,更新更多的信息,比如说湿度,那就要去修改changed()
方法的代码,这就是紧耦合的坏处。怎么解决呢?使用观察者模式,面向接口编程,实现松耦合。
观察者模式里面,
changed()
方法所在的实例对象,就是被观察者(Subject,或者叫Observable),它只需维护一套观察者(Observer)的集合,这些Observer实现相同的接口,Subject只需要知道,通知Observer时,需要调用哪个统一方法就好了:
(原文地址:https://juejin.im/post/6844903733738864654 )
function Subject(){
this.observers = [];
}
Subject.prototype = {
add:function(observer){ // 添加
this.observers.push(observer);
},
remove:function(observer){ // 删除
var observers = this.observers;
for(var i = 0;i < observers.length;i++){
if(observers[i] === observer){
observers.splice(i,1);
}
}
},
notify:function(){ // 通知
var observers = this.observers;
for(var i = 0;i < observers.length;i++){
observers[i].update();
}
}
}
function Observer(name){
this.name = name;
}
Observer.prototype = {
update:function(){ // 更新
console.log('my name is '+this.name);
}
}
var sub = new Subject();
var obs1 = new Observer('ttsy1');
var obs2 = new Observer('ttsy2');
sub.add(obs1);
sub.add(obs2);
sub.notify(); //my name is ttsy1、my name is ttsy2
上述代码中,我们创建了 Subject 对象和两个 Observer 对象,当有关状态发生变更时则通过 Subject 对象的 notify 方法通知这两个 Observer 对象,这两个 Observer 对象通过 update 方法进行更新。
在 Subject 对象添加了一系列 Observer 对象之后,还可以通过 remove 方法移除某个 Observer 对象对它的依赖。
var sub = new Subject();
var obs1 = new Observer('ttsy1');
var obs2 = new Observer('ttsy2');
sub.add(obs1);
sub.add(obs2);
sub.remove(obs2);
sub.notify(); //my name is ttsy1
发布订阅模式
发布订阅模式指的是希望接收通知的对象(Subscriber)基于一个主题通过自定义事件订阅主题,被激活事件的对象(Publisher)通过发布主题事件的方式通知各个订阅该主题的 Subscriber 对象。
function EventTarget(){
//EventTart类型有一个单独的属性handlers,用于存储事件处理程序
this.handlers = {};
}
EventTarget.prototype = {
//重新指向EventTarget函数
constructor: EventTarget,
//type:事件类型 handler: 用于处理该事件的函数
addHandler:function(type,handler){
//判断handlers对象中是否存在属性名为type的属性
if(typeof this.handlers[type] == "undefined"){
//如果不存在就将属性名为type属性的属性值设置为一个新数组,用于存放type类型的事件处理程序
this.handlers[type] = [];
}
//将handler事件处理函数添加到数组末尾
this.handlers[type].push(handler);
},
//fire()函数要接受一个至少包含type属性的对象作为参数
fire: function(event){
if(!event.target){
event.target = this;
}
//判断要触发的消息是否存在
if(this.handlers[event.type] instanceof Array){
//获取对应类型的事件处理程序
var handlers = this.handlers[event.type];
for(var i=0,len = handlers.length; i<len; i++){
//调用每个函数,并给出event对象
handlers[i](event);
}
}
},
removeHandler: function(type, handler){
//防止删除的消息不存在的情况
if(this.handlers[type] instanceof Array){
var handlers = this.handlers[type];
for(var i=0,len = handlers.length;i<len;i++){
if(handlers[i] === handler){
break;
}
}
//将i位置的事件处理程序删除
handlers.splice(i,1);
}
}
};
EventTarget类型有一个单独的属性handlers,用于储存事件处理程序。还有三个方法:
1. addHandler(),用于注册给定类型事件的事件处理程序;
2. fire(),用于触发一个事件;
3. removeHandler(),用于注销某个事件类型的事件处理程序。addHandler()方法接受两个参数: 事件类型和用于处理该事件的函数。当调用该方法时,会进行一次检查,看看handlers属性中是否已经存在一个针对该事件类型的数组;如果没有,则创建一个新的。然后使用push()将该处理程序添加到数组的末尾。
如果要触发一个事件,要调用fire()函数。该方法接受一个单独的参数,是一个至少包含type属性的对象。fire()方法给 event对象设置一个 target属性,如果它尚未被指定的话。然后它就查找对应该事件类型的一组处理程序,调用各个函数,并给出event对象。因为这些都是自定义事件,所以 event 对象上还需要的额外信息由你自己决定.
removeHandler()方法是 addhandler()的辅助,它们接受的参数一样:事件的类型和事件处理程序。这个方法搜索事件处理程序的数组找到要删除的处理程序的位置。如果找到了,则使用 break操作符退出for循环。然后使用splice方法将该项目从数组中删除.
然后使用EventTarget类型的自定义事件可以如下使用:
function handleMessage(event){
alert("message received:" + event.message);
}
//创建一个新对象
var target = new EventTarget();
//添加一个事件处理程序
target.addHandler("message",handleMessage);
//触发事件
target.fire({type:"message",message:"Hello world!"});
//删除事件处理程序
target.removeHandler("message",handleMessage);
//再次触发,应该没有处理程序
target.fire({type:"message",message:"Hello world!"});
在这段代码中,定义了 handleMessage()函数用于处理 message事件。它接受 event对象并输出 message属性。调用 target对象的 addHandler()方法并传给" message以及 handleMessage()函数。在接下来的一行上,调用了fire()函数,并传递了包含2个属性,即type和 message的对象直接量。它会调用 message事件的事件处理程序,这样就会显示一个警告框(来自 handleMessage())
然后删除了事件处理程序,这样即使事件再次触发,也不会显示任何警告框。
因为这种功能是封装在一种自定义类型中的,其他对象可以继承 EventTarget并获得这个行为,如下所示:
function Person(name , age){
EventTarget.call(this);
this.name = name;
this.age = age;
}
//组合继承方法
// function inheritPrototype(subType,superType){
// var prototype = object(superType.prototype);
// prototype.constructor = subType;
// subType.prototype = prototype;
// }
inheritPrototype(Person,EventTargetart);
Person.prototype.say = function(message){
this.fire({type:"message",message:'message'});
};
Person类型使用了寄生组合继承方法来继承 EventTarget。一旦调用了say()方法,便触发了事件,它包含了消息的细节。在某种类型的另外的方法中调用fire()方法是很常见的,同时它通常不是公开调用的。这段代码可以照如下方式使用:
function handleMessage (event)(
alert (event. target name says
event message)i
//创建新 person
var person: new Person("Nicholas" 29)
//添加一个事件处理程序
person addHandler("message", handleMessage)
//在该对象上调用1个方法,它触发消息事件
person say("Hi there.");
这个例子中的handleMessage()函数显示了某人名字(通过 event.target.name获得)的一个警告框和消息正文。当调用say()方法并传递一个消息时,就会触发 message事件。接下来,它又会调用 handleMessage()函数并显示警告框。
当代码中存在多个部分在特定时刻相互交互的情况下,自定义事件就非常有用了。这时,如果每全对象都有对其他所有对象的引用,那么整个代码就会紧密耦合,同时维护也变得很困难,因为对某个对象的修改也会影响到其他对象,使用自定义事件有助于解耦相关对象,保持功能的隔绝。在很多情况中触发事件的代码和监听事件的代码是完全分离的。
在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。
互不相识?那他们之间如何交流?
答案是,通过第三者,也就是在消息队列里面,我们常说的经纪人Broker。
发布者只需告诉Broker,我要发的消息,topic是AAA;
订阅者只需告诉Broker,我要订阅topic是AAA的消息;
于是,当Broker收到发布者发过来消息,并且topic是AAA时,就会把消息推送给订阅了topic是AAA的订阅者。当然也有可能是订阅者自己过来拉取,看具体实现。
也就是说,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。
放一张极简的图,给大家对比一下这两个模式的区别:
总结
从表面上看:
- 观察者模式里,只有两个角色 —— 观察者 + 被观察者
- 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker
往更深层次讲:
- 观察者和被观察者,是松耦合的关系
- 发布者和订阅者,则完全不存在耦合
从使用层面上讲:
- 观察者模式,多用于单个应用内部
- 发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件