单例模式在传统软件工程中使用非常广泛,单例模式的含义就是如果一个类被设计成单例类,那么从第一次创建它的对象开始,从始至终这个类都只保存一个实例对象,不会有多个类的实力对象。为了保持这个特性,所以通常的做法都是为这个单例类设置一个静态的属性,第一次创建类对象的时候,就将这个类对象保存在这个静态属性中,以后再次实例化类对象的时候,都是取出保存在静态属性中的这个对象,所以从始至终这个类都只会有一个实例对象存在,这就是单例模式。
JavaScript语言并不像传统面向对象语言那样具有静态变量。但是JavaScript依然可以实现单例模式,而且由于其动态性,会有多种实现方法。
为了能够实现静态变量,我们通常会使用闭包,我们将静态变量保存在闭包中就可以了。
var singleClass = function() {
var _instance = null; // 用于保存实例对象的静态变量
function Class() { // 类的定义
this.name = 'Leo';
this.friends = ['Jack'];
}
Class.prototype = {
constructor: Class,
sayHello: function() {
alert( 'Hello, ' + this.name );
}
}
return {
getInstance: function() {
if ( _instance === null ) { // 如果第一次实例化,就new一个然后保存起来
_instance = new Class();
}
return _instance;
}
}
}();
var single1 = singleClass.getInstance();
single1.sayHello(); // 输出'Hello, leo'
var single2 = singleClass.getInstance();
single2.name = 'Tommy'; // 设置新的名字
single2.sayHello(); // 输出'Hello Tommy'
single1.sayHello(); // 输出'Hello Tommy',single1也跟着改变,说明是同一个实例对象
这种方式要求大家统一调用getInstance才能取出实例对象,如果习惯使用new操作符去实例化对象的话,应该怎么去控制呢?
function SingleClass( name ) {
var _instance = null; // 保存实例对象的静态变量
this.name = name;
_instance = this; // 将this赋值给静态变量
function F() {};
F.prototype = SingleClass.prototype;
SingleClass = function () { // 修改构造函数,第一次实例化之后,以后的实例化直接返回保存在静态变量中的实例对象
return _instance;
}
SingleClass.prototype = new F(); // 将原型设置回新的构造函数
}
SingleClass.prototype = {
constructor: SingleClass,
sayHello: function (){
alert( 'Hello, ' + this.name );
}
}
var single1 = new SingleClass( 'Leo' );
single1.sayHello(); // 输出“Hello, Leo”
single1.name = 'Tommy';
var single2 = new SingleClass( );
single2.sayHello(); // 输出“Hello, Tommy”
使用这种方式的优点就是你不用去关心这个对象是否是单例模式,只需要按照正常的new操作符的方式就可以了。代码中的this在使用new操作符的时候,会指向新的实例对象,所以就相当于将新的实力对象赋值给了静态变量,而新的构造函数作为一个闭包的方式,可以继续使用这个静态变量。注意在修改构造函数的时候,必须使用函数表达式的方式,而不能使用函数申明的方式,因为函数申明会将内层中的SingleClass作为外层中的SingleClass中的一个变量对象,尽管他们具有相同的名字,但他们不再是指向同一个函数对象了。有个问题就是修改构造函数之后,新的构造函数的原型指向了Object,所以我们需要将原来的原型对象设置回新的构造函数,如果这点对项目来说不重要的话,也可以不需要这么做。
其实除了闭包,我们还可以有另外实现静态变量的方式,那就是类变量。JavaScript是一种基于对象的语言,即使是函数,也是Function类型的一个对象实例。如果SingleClass也是一个对象,那么我们就可以为这个对象赋值属性,那么这个属性就可以看成是类变量了。类变量的特点就是不用实例化就可以使用,而且是通过类调用,不是通过对象实例调用,这点更符合面向对象语言的特点。所以将上面的代码修改一下就变成了:
function SingleClass( name ) {
if ( null !== SingleClass.Instance ) { // 如果“类变量”不为空,就返回保存在类变量中的实例对象
return SingleClass.Instance;
}
this.name = name;
SingleClass.Instance = this; // 将实例对象保存在类变量中
}
SingleClass.prototype = {
constructor: SingleClass,
sayHello: function (){
alert( 'Hello, ' + this.name );
}
}
SingleClass.Instance = null; // 用于保存对象实例的“类变量”
关于new操作符的特点,如果没有显示定义返回值,就返回新的实例对象,所以第一次实例化的时候,就会返回this。第二次实例化的时候,会进入if中的代码,那么就会显示返回“类变量”中保存的实例对象了。