第四章:单例模式
文章目录
定义:保证一个类仅有一个实例,并提供一个访问它的全局变量。
在有些时候,一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的Window对象等。
应用举例:单击登陆按钮的时候,页面中会出现一个唯一的登陆浮窗,无论单击多少次登陆按钮,这个浮窗也只会被创建一次,那么这个浮窗就适合使用单例模式来创建
4.1 实现单例模式
实现原理:用一个变量来标识当前是否已经为某个类创建过对象,如果是,则在下一次获取该实例的时候,直接返回之前创建的对象。
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('seven1');
var b = Singleton.getInstance('seven2');
alert(a===b); // true
这里实现了一个简单的单例模式,但是这种方式增加了这个类的“不透明性”,Singleton
类的使用者必须知道这是一个单例类,与以往通过new xxx
来获取对象不同,这里要通过Singleton.getInstance()
来获取对象
4.2 透明的单例模式
在页面中创建唯一的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("seven1");
var b = new CreateDiv("seven2");
alert(a===b); // true
解析:
- 这里使用了匿名自执行函数与闭包。自执行函数主要是为了创建一个函数作用域,将
CreateDiv
对象的创建与instance
合并。 - 为了使通过
new
创建的对象的this能够指向唯一实例,使用return instance=this
。 - 使用
instance
闭包保存唯一实例,如果instance
不为空,返回instance
;如果为空,就遵循new
创建实例的规则,创建唯一实例,并将其送到instance
中保存。
注意:
- 闭包保存唯一实例
- 每次new的时候,其实也会创建新的实例,但是,这个新的实例并不一定保存。
- 主要看
instance
这个唯一实例的状态
4.3 通过代理实现单例
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("seven1");
var b = new proxySingletonCreateDiv("seven2");
console.log(a === b); // true
将负责管理单例的逻辑转移到了代理类proxySingletonCreateDiv
中。
这样一来,CreateDiv
就变成了一个普通的类,和proxySingletonCreateDiv
组合起来可以达到单例模式的效果。
其实与上面的实现透明单例模式的代码差不多,只是在其基础之上做了功能的分离。
分离之后,如果某天需要用到CreateDiv
这个类,在页面中创建超级多个div实例,就会很方便。
———— 这符合单一职责原则
4.4 JavaScript中的单例模式
上面几种单例模式,更多是接近传统面向对象语言中的实现,单例从“类”中创建而来。
然而JS其实是一门无类语言
,在SJ中创建对象之前并不需要创建一个“类”
。
全局变量不是单例模式,但是在JavaScript开发中,经常把全局变量当作单例模式来使用
var a = {};
通过这种凡事创建的对象,独一无二且能在全局环境中使用,满足单例模式的两个条件。
但是全局变量中容易造成命名空间的污染
在我看来,完全可以利用const生成一个全局唯一变量。或者使用
Promise
也可以??只要保证全局唯一就好了。
4.4.1 使用命名空间
适当使用命名空间,并不会杜绝全局变量,但可以减少全局变量的数量。
var namespace1 = {
a:function(){
alert(1);
},
b:function(){
alert(2)
}
}
4.4.2 使用闭包封装私有变量
将一些变量封装在闭包内部,只暴露一些接口与外界通信
var user = (function(){
var _name = 'Seven',
_age = '29';
return {
getUserInfo:function(){
return _name + "-" + _age;
}
}
})();
这样实现的单例,不需要通过额外的变量instance
来判断是否需要创建构造函数的实例。通过user调用闭包中的数据,本身就是全局唯一的。
我觉得这并没有改善命名空间的污染。之前是在全局中定义了一个对象作为单例,对象名可能会出现变量污染,现在不过是换了一个作用域存储单例的数据内容,这个user同样可能会出现变量污染
4.5 / 惰性单例
惰性单例是单例模式的重点,在实际开发中非常有用。
特点:惰性单例是指在需要的时候才创建对象实例。比如开头第一段代码,instance
总是在我们调用Singleton.getInstance
的时候才会被创建,而不是在页面加载的时候就创建,那就是惰性单例。
不过,前面的代码是基于“类”的单例模式。而基于类的单例模式在JavaScript中并不适用。
例:登陆窗口
1、 登陆窗口总是在一开始就被创建好,只是通过css属性进行隐藏
2、 登陆窗口在点击登陆
按钮的时候才被创建。
一开始就被创建好并没有必要,有的用户并没有登陆的需求,只需通过游客模式浏览一些信息,那么这样就会白白浪费一些DOM节点。
HTML:
<button class="login">登陆</button>
JavaScript:
var login = document.getElementsByClassName("login")[0];
login.addEventListener("click",function(){
var div = createLoginLayer();
div.style.display = "block";
});
var createLoginLayer = (function(){
var instance = null;
var createLoginLayer = function(){
if(instance){
return instance;
}
var div = document.createElement("div");
div.innerHTML = "我是登陆浮窗";
div.style.display = "none";
document.body.appendChild(div);
return instance = div;
};
return createLoginLayer;
})();
点击按钮会生成html结构并添加到页面中去。
不论点击多少次,只会生成一个登陆浮窗。
4.6 通用的惰性单例
- 上面的代码虽然实现了一个可用的惰性单例,但是违反了单一职责原则,创建和管理单例的逻辑都放在
createLoginLayer
对象内部。 - 如果我们并不是用来创建登陆浮窗,就需要将这个函数几乎照抄,修改要添加的HTML结构
要实现通用的惰性单例代码,需要将创建和管理单例的逻辑分离,并将管理单例的代码抽象出来,这部分一般是不变的。
HTML结构:
<button class="login">登陆</button>
JavaScript:
// 创建HTML结构
var createDiv = function(){
var div = document.createElement("div");
div.innerHTML = "Please Login In";
div.style.display = "none";
document.body.appendChild(div);
return div;
};
// 单例控制
var getSingle = (function(){
var result ;
return function(fn){
if(result){
return result;
}
return result = fn();
};
})();
var login = document.getElementsByClassName("login")[0];
login.onclick= function(){
var div = getSingle(createDiv);
div.style.display = "block"
};
把创建实例对象的职责和管理单例的职责分别放置在两个方法里,这两个方法可以独立变化而互不影响,当它们连接在一起的时候,就完成了创建唯一实例对象的功能。