最近在捣鼓Reflux和Redux,用到了发布/订阅模式。之前在学习jQuery插件编写时用到了单例模式。于是乎想着把常用的设计模式做下总结,深化下自己的理解。
单例模式
单例模式:确保类只能被实例化一次。
在该实例不存在的情况下,可以通过一个方法创建一个类来实现创建类的新实例;如果实例已经存在,它直接返回该对象的引用。
在JavaScript里,实现单例的方式有很多种,其中最简单的一个方式是使用对象字面量的方法,其字面量里可以包含大量的属性和方法:
<script type="text/javascript">
var mySingleton = {
name:"lisi",
age:23,
sayName:function(){
console.log("Hello lisi");
}
};
</script>
如果以后要扩展该对象,你可以添加自己的私有成员和方法,然后使用闭包在其内部封装这些变量和函数声明。只暴露你想暴露的public成员和方法,样例代码如下:
<script type="text/javascript">
var mySingleton = function(){
//声明私有变量和方法
var name = "lisi";
function showName(){
console.log(name);
}
//公有变量和方法(可以访问私有变量和方法)
return {
publicMethod:function(){
showName();
},
publicVar:"我是公有变量"
};
};
var single = mySingleton();
single.publicMethod();//lisi
console.log(single.publicVar);//我是公有变量
</script>
<script type="text/javascript">
var mySingleton = (function(){
var instance;//保存实例的引用
function init(){
//私有变量和函数
var privateVar = "lisi";
function privateMethod(){
console.log(privateVar);
}
return {
//公有变量和函数
publicMethod :function(){
privateMethod();
},
publicVar:"我是公有变量"
};
}
return {
getInstance:function(){
if(!instance){
//只有在用到的时候才进行初始化
instance = init();
}
//如果存在则直接返回,不存在则新初始化一个实例
return instance;
}
};
})();
mySingleton.getInstance().publicMethod();//lisi
console.log(mySingleton.getInstance().publicVar);//我是公有变量
</script>
模块模式
该模式使用闭包封装私有状态和组织。它提供了一种包装混合公有/私有方法和变量的方式,防止其泄漏至全局作用域,并与别的开发人员的接口发生冲突。通过该模式,只需返回一个公有API,其他的一切则都维持在私有闭包里。
jQuery等类库封装就采用了模块模式。
Demo:
<script type="text/javascript">
var myModule = (function(){
//私有变量
var myPrivateVar = 12;
//私有函数
var myPrivateMethod = function(foo){
console.log(foo);
};
return {
//公有变量
myPublicVar:"foo",
//调用私有变量和方法的公有函数
myPublicMethod:function(bar){
//增加私有变量计数器值
myPrivateVar++;
myPrivateMethod(bar);
}
};
})();
console.log(myModule.myPublicVar);//foo
myModule.myPublicMethod("hello world");//hello world
</script>
观察者模式
观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
使用观察者模式的好处:
- 支持简单的广播通信,自动通知所有已经订阅过的对象。
- 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
- 发布者与订阅者耦合性降低,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件名,只要发布者的事件名不变,它不管发布者如何改变。
JS里对观察者模式的实现是通过回调来实现的
Demo:
<script type="text/javascript">
function Observer() {
this.fns = [];//存放回调的数组
}
Observer.prototype = {
//增加一个订阅
subscribe: function (fn) {
this.fns.push(fn);
},
//删除一个订阅
unsubscribe: function (fn) {
//过滤退订的函数
this.fns = this.fns.filter(function (el) {
if (el !== fn) {
return el;
}
}
);
},
//发布
public: function (args, thisObj) {
var scope = thisObj || window;
// console.log(scope);//window
this.fns.forEach(function (el) {
el.call(scope, args);
}
);
}
};
//创建一个pubsub对象
//该对象原型上有发布、订阅、退订三个方法
var o = new Observer();
var f1 = function (data) {
console.log('王五: ' + data + ', 赶紧干活了!');
};
var f2 = function (data) {
console.log('李四: ' + data + ', 找他加点工资去!');
};
//订阅
o.subscribe(f1);
o.subscribe(f2);
//发布
o.public("老板回来了!");
//王五退订
o.unsubscribe(f1);
//再次发布,只会触发李四对应的回调
o.public("老板回来了!");
</script>
构造函数模式
对于构造函数大家都不陌生,构造函数用于创建特定类型的对象——不仅声明了使用的对象,构造函数还可以接受参数以便第一次创建对象的时候设置对象的成员值。我们可以自定义自己的构造函数,然后在里面声明自定义类型对象的属性或方法。
Demo:
<script type="text/javascript">
function Person(name,age){
this.name = name;
this.age = age;
this.sayInfo = function(){
console.log(this.name + "---" + this.age);
}
}
var person1 = new Person("lisi",23);
var person2 = new Person("wangwu",24);
person1.sayInfo();//lisi---23
person2.sayInfo();//wangwu---24
//这样貌似看上去很完美,但是sayInfo()在每次创建对象的时候都重新定义了(下面代码返回false表明,两个实例的sayInfo不是同一个),最好的方法是让所有Person类型的实例都共享这个sayInfo()方法,这样如果有大批量的实例的话,就会节约很多内存。
console.log(person1.sayInfo == person2.sayInfo);//false
</script>
原型+构造函数
<script type="text/javascript">
function Person(name,age){
this.name = name;
this.age = age;
}
//这样一来,所有实例都共享sayInfo方法
Person.prototype.sayInfo = function(){
console.log(this.name + "---" + this.age);
}
var person1 = new Person("lisi",23);
var person2 = new Person("wangwu",24);
person1.sayInfo();//lisi---23
person2.sayInfo();//wangwu---24
console.log(person1.sayInfo == person2.sayInfo);//true
</script>
工厂模式
工厂模式就好比现实生活中的工厂可以产生大量相似的产品,工厂模式适用于:做同样的事情,实现相同的效果。其解决了创建多个相似对象的问题。
Demo:
<script type="text/javascript">
function createPerson(name,age,job){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayInfo = function(){
console.log(this.name+"--"+this.age+"--"+this.job);
};
return obj;
}
var person1 = createPerson("lisi",23,"student");
var person2 = createPerson("wangwu",34,"worker");
person1.sayInfo();//lisi--23--student
person2.sayInfo();//lisi--23--student
//返回都是object 无法识别对象的类型 不知道他们是哪个对象的实列
console.log(typeof person1);//object
console.log(typeof person2);//object
</script>
函数createPerson能接受三个参数name,age,job等参数,可以无数次调用这个函数,每次返回都会包含三个属性和一个方法的对象。
工厂模式是为了解决多个类似对象声明的问题;也就是为了解决实列化对象产生重复的问题(避免了产生大量的重复代码)。
优点:解决了创建多个相似对象的问题
缺点:没有解决对象识别的问题(即怎样确定一个对象的类型)
后续再跟进。。
参考博文:
Javascript常用的设计模式详解
深入理解JavaScript系列