Gang of Four(四人帮)编著的《设计模式》一书,相信大家都很熟悉的(即使没有看过也有听说过吧)。可是,这些设计模式对于静态的、强类型的、基于类的的语言 (比如Java,C++)来说是很容易实现的,对于JavaScript这样的语言来说,可能是微不足道的或者是没必要的。
在 JavaScript里面,常用的设计模式有单体模式(singleton)、装饰器模式(decorator)、工厂模式(builder)等。用得最 多的也许就是单体模式了。比如我们上淘宝买东西的那个购物车,就是一个单体了。试想一下,如果不是单体的,每次新增一件商品,就实例化一个新的购物车,那 最终怎么结账呢?到底应该以哪个购物车的为准呢?
下面我们一起来看一下JavaScript是如何实现单体模式。
我们提到JavaScript中的“单体”的时候,意思是使用模块模式创建一个单一对象。如果直接通过字面量的方法创建对象,肯定是实现不了单体的。如下:
var a = {age: 1};
var b = {age: 1};
console.log(a === b); //false
即使对象a和b的内容是一样的,但是他们却是两个不同的对象。因为通过字面量的方法,每次都会创建一个新的对象。所谓单体,我们的目标就是要使得a === b成立。
由于目标是创建单个实例对象,我们有个好的方法就是使用立即执行函数所创建的的闭包把创建实例对象的代码放到里面去。好了,说了一大篇的废话了,现在直接上代码。我们就以实现一个简单的购物车单体为例:
var ShoppingCar = (function() {
//这个是由购物车构造器所创建的实例
var instance;
//购物车的构造器函数
function Trolley() {
this.date = new Date().getDate(); //实例属性,当前日期
}
//原型属性,一个返回当前日期的方法
Trolley.prototype.getDate = function() {
return this.date;
};
//暴露出去的公共API
return function() {
//如果实例不存在,那么就调用Trolley构造器实例化一个
if(!instance) {
instance = new Trolley();
}
//将实例返回外部
return instance;
};
}());
下面我们一起来看一下上面的代码。这里面的购物车实例instance保护在了闭包的内部。同一个闭包里面包含了局部的Trolley构造器函数,他 就一个常规的构造器一样工作,在其中,我们可以给this和Trolley.prototype添加好多的属性(这里我们只是添加了个日期属性和获取日期 的方法,当然你可以在里面添加好多好多的属性和方法)。
我们注意到,最外层是一个自执行函数,它执行结果是返回一个函数,我们把他的执行结果赋值给了全局的ShoppingCa变量。然后就可以使用new来调用这个全局变量了。
下面我们来检测一下它是否真的创建了单体,即实例化两次,分别赋值到变量a和b上,然后比较a和b是否相等即可,如下:
var a = new ShoppingCar();
var b = new ShoppingCar();
console.log(a === b); //true
虽然我们调用了两次ShoppingCar创建了两次购物车的实例,但是,这两个实例实质上是相同的,也就是他们是同一个对象实例。OK,我们在JavaScript里面是实现单体模式的任务已经完成了!