单例模式又被称为单体模式,它是一种非常基本但使用频率很高的设计模式。它的基本设计思想是将代码组织为一个逻辑单元,这个逻辑单元中的代码只能通过单一的变量进行访问。通俗点说就是一个对象只能被实例化一次。由于它确保了访问入口的唯一,所以可以保证所有的代码使用到相同的全局资源。
基本的单例模式
最基本的单例模式实际上就是一个对象字面量,当然,你也可以称它为一个命名空间。
var singleExample = {
attribute1: "something",
attribute2: "another",
method1: fucntion(){
},
method2:fucntion(){
}
}
成员中的所有属性和方法都要通过 singleExample 去访问,这样做的优势很明显,可以减少全局下的变量定义。而且 singleExample 可以作为你的独有代码空间来使用,防止与其他人的代码有冲突。你还可以按照这样的方式对不同的功能模块进行定义,这样更加方便管理和维护。
含私有成员的单体
关于单体模式,比较严格定义是:单体是一个只能被实例化一次并且可以通过一个众所周知的访问点访问的类。按照这种定义的话,上边的那种写法我们并不能成为单体模式,因为它只是一个实例并不是一个类。不过,关于单体还有一种被大家普遍认可的广义定义:单体是一个用来划分命名空间并将一批相关方法和属性组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次。
当然,使用单体模式的时候你完全可以变通一些,不必每次都写一个字面量。可以结合一些私有成员使自己的代码更加隐私,纯净。
var NameSpace = {};
NameSpace.Singleton = (functiono(){
// private members
var privateAttr1 = false;
var privateAttr2 = [1,2,3];
fucntion privateMethod1(){
...
}
function privateMethod2(){
...
}
// public members
return {
publicAttr1:true,
publicAttr2:10,
publicMethod1:function(){
privateMethod1();
},
publicMethod2:function(){
}
}
})()
按照这种结合私有成员的方式,你可以放心大胆的在自己的私有作用域内定义变量,处理逻辑。只要最后将他们返回出去就好。
惰性单体
实际上我们上边所提到的单例都是在脚本被加载的时候就直接创建的,有时候为了提高性能我们希望可以自己控制单体被实例化的时机,只在我们需要使用的时候才将其进行实例化这样可以提高我们的内存性能。所以我们需要借助构造函数实现。
var Singleton = (function () {
var instantiated;
function init() {
/*单例内容*/
return {
publicMethod: function () {
console.log('hello world');
},
publicProperty: 'test'
};
}
return {
getInstance: function () {
if (!instantiated) {
instantiated = init();
}
return instantiated;
}
};
})();
/*调用公有的方法来获取实例:*/
Singleton.getInstance().publicMethod();
实例化一次的类
在单例模式的定义中我们一直强调它应该是一个实例对象或者类,并且如果它是类只能被实例化一次。那么具体是怎么实现的呢?
方式一
function Universe() {
// 判断是否存在实例
if (typeof Universe.instance === 'object') {
return Universe.instance;
}
// 其它内容
this.start_time = 0;
this.bang = "Big";
// 缓存
Universe.instance = this;
// 隐式返回this
}
// 测试
var uni = new Universe();
var uni2 = new Universe();
console.log(uni === uni2); // true
方式二
function Universe() {
// 缓存的实例
var instance = this;
// 其它内容
this.start_time = 0;
this.bang = "Big";
// 重写构造函数
Universe = function () {
return instance;
};
}
// 测试
var uni = new Universe();
var uni2 = new Universe();
uni.bang = "123";
console.log(uni === uni2); // true
console.log(uni2.bang); // 123
方式三
function Universe() {
// 缓存实例
var instance;
// 重新构造函数
Universe = function Universe() {
return instance;
};
// 后期处理原型属性
Universe.prototype = this;
// 实例
instance = new Universe();
// 重设构造函数指针
instance.constructor = Universe;
// 其它功能
instance.start_time = 0;
instance.bang = "Big";
return instance;
}
// 测试
var uni = new Universe();
var uni2 = new Universe();
console.log(uni === uni2); // true
// 添加原型属性
Universe.prototype.nothing = true;
var uni = new Universe();
Universe.prototype.everything = true;
var uni2 = new Universe();
console.log(uni.nothing); // true
console.log(uni2.nothing); // true
console.log(uni.everything); // true
console.log(uni2.everything); // true
console.log(uni.constructor === Universe); // true
方式四
var Universe;
(function () {
var instance;
Universe = function Universe() {
if (instance) {
return instance;
}
instance = this;
// 其它内容
this.start_time = 0;
this.bang = "Big";
};
} ());
//测试代码
var a = new Universe();
var b = new Universe();
alert(a === b); // true
a.bang = "123";
alert(b.bang); // 123
模块化中的应用
个人认为单体模式在模块化中才能真正发挥它的优势,无论你使用的是哪种模块化规范,只需要将自己的代码组织成一个单体实例,然后使用类似 module.expoorts = 单体名将它导出。
很多的npm包也是以单体的模式进行设计和暴露的,只需要在需要的地方将它引入就可以使用其内部的方法。当然,在模块化中有一点好处就是它保证了在其他模块中引入使用别的模块的时候不能改变原模块的代码。这样的好处是当前文件中的修改不会影响其他引入了该模块的模块。
参考内容
《JavaScript设计模式》 Ross Harmes Dustuin Diaz
http://www.cnblogs.com/TomXu/archive/2012/02/20/2352817.html