- 单例模式的定义是:
保证一个类仅有一个实例,并提供一个访问它的全局访问点
。 - 原理:用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象
1. 单例模式
//第一种写法
var Singleton1 = function (name) {
this.name = name;
this.instance = null;
};
Singleton1.getInstance = function (name) {
if (!this.instance) {
this.instance = new Singleton1(name);
}
return this.instance;
};
var a = Singleton1.getInstance('sven1');
var b = Singleton1.getInstance('sven2');
alert(a === b); //true
//第二种写法
var Singleton2 = function (name) {
this.name = name;
}
Singleton2.getInstance = (function () {
var instance = null;
return function (name) {
if (!instance) {
instance = new Singleton2(name);
}
return instance;
}
})();
var c = Singleton2.getInstance('jun');
var d = Singleton2.getInstance('bin');
alert(c === d); //true
- 上面实现的缺点:这种方式相对简单,但有一个问题,就是增加了这个类的“不透明性”,Singleton 类的使用者必须知道这是一个单例类,跟以往通过new XXX 的方式来获取对象不同,这里偏要使用 Singleton.getInstance 来获取对象
2. 透明的单例模式
- 我们现在的目标是实现一个“透明”的单例类,用户从这个类中创建对象的时候,可以像使 用其他任何普通类一样。在下面的例子中,我们将使用 CreateDiv 单例类,它的作用是负责在页面中创建唯一的 div 节点
var CreateDiv = (function () {
var instance;
var CreateDiv = function (html) {
if (instance) {
return instance;
}
this.htme = 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');
console.log(a === b);//true
- 缺点:为了把 instance 封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的 Singleton 构造方法,这增加了一些程序的复杂度
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 ProxySingtonCreateDiv = (function () {
var instance;
return function (html) {
if (!instance) {
instance = new CreateDiv(html);
}
return instance;
}
})();
var c = new ProxySingtonCreateDiv('kobe');
var d = new ProxySingtonCreateDiv('curry');
console.log(c === d); //true
4. JavaScript中的单例模式
前面提到的几种单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从“类”中创建而来。在以类为中心的语言中,这是很自然的做法。比如在 Java 中,如果需要某个对象,就必须先定义一个类,对象总是从类中创建而来的。但 JavaScript 其实是一门无类(class-free)语言,也正因为如此,生搬单例模式的概念并无意义。在 JavaScript 中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象,为什么要为它先创建一个“类”呢?这无异于穿棉衣洗澡,传统的单例模式实现在 JavaScript 中并不适用。
- 单例模式的核心是确保只有一个实例,并提供全局访问。
4.1 惰性单例
- 惰性单例指的是在需要的时候才创建对象实例。(惰性单例是单例模式的重点)
//基于“类”的惰性单例模式
Singleton.getInstance = (function () {
var instance = null;
return function (name) {
if (!instance) {
instance = new Singleton(name);
}
return instance;
};
})();
- 案例:点击按钮的时候才创建dom,并且符合单例模式
<html>
<body>
<button id="loginBtn">登录</button>
</body>
<script>
var createLoginLayer = (function () {
var div;
return function () {
//这段代码仍然是违反单一职责原则的,
//创建对象和管理单例的逻辑都放在
//createLoginLayer对象内部
if (!div) {
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>
我们需要把不变的部分隔离出来,先不考虑创建一个 div和创建一个 iframe 有多少差异,管理单例的逻辑其实是完全可以抽象出来的,这个逻辑始终是一样的:用一个变量来标志是否创建过对象,如果是,则在下次直接返回这个已经创建好的对象 。
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 createSingleLayer = getSingle(createLoginLayer)
document.getElementById('loginBtn').onclick = function () {
var loginLayer = createLoginLayer()
loginLayer.style.display = 'block'
}
// 下面我们再试试创建唯一的 iframe 用于动态加载第三方页面:
var createSingleIframe = getSingle(function () {
var iframe = document.createElement('iframe')
document.body.appendChild(iframe)
return iframe
})
document.getElementById('loginBtn').onclick = function () {
var loginLayer = createSingleIframe()
loginLayer.src = 'http://baidu.com'
}