1359 字
7 分钟
单例模式

单例模式#

保证一个类仅有一个实例,并提供给一个访问它的全局访问点。

1. 实现简单的单例模式#

var Singleton = function(name){
this.name = name;
}
Singleton.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 a1 = Singleton.getInstance('a1');
var a2 = Singleton.getInstance('a2');
a1 === a2 // true;

或者直接用闭包

var Singleton = function(name){
this.name = name;
}
Singleton.prototype.getName = function(){
altert(this.name);
}
Singleton.getInstance = (function(){
var instance = null;
return function(name){
if(!instance){
instance = new Singleton(name);
}
return this.instance;
}
})();

这种方式相对简单,但是增加了这个类的“不透明性”,这个类的使用者必须知道这个是一个单例,与之前的new一个类不同,使用者要用getinstance来获取对象

2.透明的单例模式#

目标:实现一个“透明的单例类”,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。

下面例子用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 a1 = new CreateDiv('a1');
var a2 = new CreateDiv('a2');
a1 === a2 // true;

虽然透明了,但有缺点,为了把instance封起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的Singleton构造方法,增加了复杂度。

假如某天我们需要利用这个类,再页面中创建许多div,则就要让这个单例类变成一个普通的可产生多个实例的类,那我们必须得改写CreateDiv构造函数,把控制创建唯一对象的那段去掉。

3.用代理实现#

由上可以知道,要想使得这个单例类变成一个普通的可以产生多个实例的类,那我们必须得改。

这时候我们把负责管理单例的代码移除出去,使他成为一个普通的创建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 a1 = new ProxySingletonCreateDiv('a1');
var a2 = new ProxySingletonCreateDiv('a2');
a1 === a2; // true;

这两个连起来就就可以就可以实现单例模式的实现,通过代理类加上一个CreateDiv普通类,组合

4. js中的单例模式#

js是一门无类语言。传统的单例模式实现再js中并不适用

单例模式的核心是确保只有一个实例,并提供全局访问

全局变量不是单例模式,单再js开发中,我们经常会把全局变量当成单例来使用

var a ={};

但是这样变量容易被覆盖。

因此需要降低全局变量命名的污染。

4.1 使用命名空间#

var namespace = {
a:function(){
...
},
b:function(){
...
}
}

4.2 使用闭包封装私有变量#

这种方法把一些变量封装再闭包内部,只暴露接口与外界通信

var user = (function(){
var _name = 'seven',_age = 29;
return {
getUserInfo:function(){
return _name + '-' + _age;
}
}
})();

我们用下划线约定_name 和 _age。

5.惰性单例#

惰性单例指的是需要的时候才去创建的对象实例。

例如:我们需要点击一个按钮显示一个弹窗,这个浮窗再这个页面里头是唯一的,不可能同时出现两个

**方法一:**在页面加载之后就去创建,然后点击btn把他显示出来

<html>
<body>
<button id="loginBtn">
登录
</button>
</body>
<script>
var loginLayer = (function(){
var div = document.createElement('div');
div.innerHTML = '我是登录浮窗';
div.style.display = 'none';
document.body.appendChild(div);
return div;
})();
document.getElementById('loginBtn').onclick = function(){
loginLayer.style.display = 'block'
}
</script>
</html>

,但是如果有时候不需要就不需要浪费空间去创建节点,需要改进

现在是点击的时候才会去创建节点

<html>
<body>
<button id="loginBtn">
登录
</button>
</body>
<script>
var loginLayer = function(){
var div = document.createElement('div');
div.innerHTML = '我是登录浮窗';
div.style.display = 'none';
document.body.appendChild(div);
return div;
};
document.getElementById('loginBtn').onclick = function(){
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block'
}
</script>
</html>

但是虽然实现了惰性,这样就失去了单例的效果,频繁的增删不太合适。

此时可以用一个变量判断是否已经创建过浮窗了。

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;
}
})()
document.getElementById('loginBtn').onclick = function(){
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block'
}

但是这个却违反了单一职责原则,这里创建和管理单例的逻辑全部都放在了一个func里头,但下次如果要创建iframe或者其他的东西,那就只能照抄代码了

因此把不变的部分分出来,把管理单例的逻辑抽出来,之后传创建的逻辑就行了。

var getSingle = function(fn){
var result ;
return function(){
return result || result = fn.apply(this,arguments);
}
}

接下来就可以用于创建弹窗的方法用于参数fn的形式传入getSingle,创建啥都行,用result保存fn的己算结果。result身在闭包中,永远不会销毁,之后如果result有值,直接返回

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('loginBtn').onclick = function(){
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block'
}
// 创建ifame啥的也是一样
单例模式
https://nollieleo.github.io/posts/单例模式/
作者
翁先森
发布于
2020-08-23
许可协议
CC BY-NC-SA 4.0