单例模式
单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我 们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少 次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。
实现单例模式
// 用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象
var Singleton = function( name ){
this.name = name;
};
Singleton.prototype.getName = function(){
alert ( this.name );
};
Singleton.getInstance = (function(){
var instance = null;
return function( name ){
if ( !instance ){
instance = new Singleton( name );
}
return instance;
}
})();
- 缺点:
增加了这个类的“不透明性”,Singleton 类的使用者必须知道这是一个单例类, 跟以往通过 new XXX 的方式来获取对象不同,这里偏要使用 Singleton.getInstance 来获取对象。
透明的单例模式
目标是实现一个“透明”的单例类,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。例如使用 CreateDiv 单例类,它的作用是负责在页面中创建唯一的 div 节点
var CreateDiv = (function(){
var instance;
var CreateDiv = function( html ){
if ( instance ){
return instance;
}
this.html = html;
this.init();
return instance = this;
};
CreateDiv.prototype.init = function(){
var div = document.createElement( 'div' ); div.innerHTML = this.html;
document.body.appendChild( div );
};
return CreateDiv;
})();
- 缺点
- 为了把 instance 封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回
真正的 Singleton 构造方法,这增加了一些程序的复杂度,阅读起来也不是很舒服。
- 为了把 instance 封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回
用代理实现单例模式
我们依然使用之前的代码,首先在 CreateDiv 构造函数中,把负责管理单例的代码移除 出去,使它成为一个普通的创建 div 的类
var CreateDiv = function( html ){
this.html = html;
this.init();
};
CreateDiv.prototype.init = function(){
var div = document.createElement( 'div' );
div.innerHTML = this.html;
document.body.appendChild( div );
};
引入代理类 proxySingletonCreateDiv
var ProxySingletonCreateDiv = (function(){
var instance;
return function( html ){
if ( !instance ){
instance = new CreateDiv( html );
}
return instance;
}
})();
var a = new ProxySingletonCreateDiv( 'sven1' );
var b = new ProxySingletonCreateDiv( 'sven2' );
alert( a === b );
JavaScript 中的单例模式
JavaScript 是一门无类(class-free)语言,生搬单例模式的概念并无意义。
单例模式的核心是确保只有一个实例,并提供全局访问。
全局变量不是单例模式,但在 JavaScript 开发中,我们经常会把全局变量当成单例来使用,例如:
var a = {};
当用这种方式创建对象 a 时,对象 a 确实是独一无二的。如果 a 变量被声明在全局作用域下, 则我们可以在代码中的任何位置使用这个变量,全局变量提供给全局访问是理所当然的。这样就 满足了单例模式的两个条件。
缺点: 容易造成命名空间污染
相对降低全局变量带来命名污染的方式:
- 使用命名空间
适当地使用命名空间,并不会杜绝全局变量,但可以减少全局变量的数量。例如:
var namespace1 = {
a: function(){
alert (1);
},
b: function(){
alert (2);
}
};
或者(引自 Object-Oriented JavaScrtipt 一书)动态地创建命名空间
var MyApp = {};
MyApp.namespace = function( name ){
var parts = name.split( '.' );
var current = MyApp;
for ( var i in parts ){
if ( !current[ parts[ i ] ] ){
current[ parts[ i ] ] = {};
}
current = current[ parts[ i ] ];
}
};
MyApp.namespace( 'event' );
MyApp.namespace( 'dom.style' );
console.dir( MyApp ); // 上述代码等价于:
var MyApp = {
event: {},
dom: {
style: {}
}
};
- 使用闭包封装私有变量
这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信:
var user = (function(){
var __name = 'sven',
__age = 29;
return {
getUserInfo: function(){
return __name + '-' + __age; }
}
}
})();