TIP
单例模式的定义是: 保证一个类仅有一个实例,并提供一个访问它的全局访问点
# 实现单例模式
实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。
var Singleton = function (name) {
this.name = name;
this.instance = null
}
Singleton.prototype.getName = function () {
alert(this.name)
}
Singleton.getInstance = function (name) {
if (!this.instance) {
this.instance = new Singleton(name)
}
return this.instance
}
var a = Singleton.getInstance('sven1')
var b = Singleton.getInstance('sven2')
alert(a === b)
# 透明的单例模式
“透明”的单例类,指的是用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。
下面的例子是使用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
})()
var a = new CreateDiv('sven1')
var b = new CreateDiv('sven2')
alert(a === b) // true
上面这段代码的缺点:
- 为了把instance封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的构造方法,增加了一些程序的复杂度,阅读起来也不是很舒服
- CreateDiv构造函数负责了两件事情。一个是创建对象和执行init方法,一个是保证只有一个对象,这不符合"单一职责"原则
# 用代理实现单例模式
为了解决上面提到的缺点,我们改造一下透明的单例模式中的例子,把负责管理单例的代码移出去,使它成为一个普通的创建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 );
};
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 ); //true
上面的代码同样也是缓存代理的应用之一,后面的章节会详细说明
# JavaScript中的单例模式
WARNING
之前讲解的单例模式都是基于"类"创建单例对象。但是Javascript是一门无类的语言,生搬上面的单例模式并无意义。传统的单例模式实现在JavasSript中并不适用。
单例模式的核心是确保只有一个实例,并提供全局访问
var a = {}
在JavaScript中,以上代码其实就可以符合单例模式的要求。但是这种写法会有很多问题,比如容易造成命名空间污染,很容易被不小心覆盖。 下面使用几种方式来降低全局变量带来的命名污染
# 使用命名空间
var myApp = {};
myApp.namespace = function (name) {
var list = name.split(".");
var current = myApp;
for(var i in list){
if(!current[i]){
current[list[i]] = {};
}
current = current[list[i]];
}
}
myApp.namespace("dom.name");
myApp.namespace("person.class.row");
console.log(myApp);
# 使用闭包封装私有变量
关闭闭包的说明及案例可以查看闭包和高阶函数
# 惰性单例
TIP
惰性单例指的是在需要的时候才创建对象实例。
惰性单例是单例模式的重点,这种技术在实际开发中非常有用。
应用场景:创建唯一的登录浮窗
var getSingle = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this, arguments));
}
};
var createLoginLayer = (function () {
var div;
return function () {
if (!div) {
var div = document.createElement( 'div' );
div.innerHTML = '我是登录浮窗';
div.style.display = 'none';
document.body.appendChild( div );
}
return div
}
})();
var createSingleLoginLayer = getSingle(createLoginLayer);
document.getElementById( 'loginBtn' ).onclick = function(){
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
};
以上代码还存在两个问题
- 违反了单一职责原则
- 扩展性低
# 通用的惰性单例
为了解决上面例子存在的问题,改造一下代码
var getSingle = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this,arguments))
}
};
var createLoginlayer = function () {
var div = document.createElement("div");
div.innerHTML = '我是登录浮窗';
div.style.display = "none";
document.body.appendChild(div);
return div
}
var createSingleLoginlayer = getSingle(createLoginlayer);
document.getElementById("btn").addEventListener("click",function(){
var loginlayer=createSingleLoginlayer("我是窗体");
loginlayer.style.display="block";
})
# 总结
创建对象和管理单例的职责分开成两个方法,组合使用,威力无穷😂