单例模式
单例模式的定义是:保证一个类仅有一个实例,并提供了一种访问其唯一的对象的方式。
单例模式是一种常用的模式之一,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。使用传统面向对象思想简单的实现一个单例模式:
var Singleton = function (name) {
this.name = name;
}
Singleton.prototype.getName = function () {
return this.name;
}
Singleton.getInstance = (function () {
var instance = null;
return function (name) {
if(instance == null) {
instance = new Singleton(name);
}
return instance;
}
})();
var instance1 = Singleton.getInstance("xiaoming");
var instance2 = Singleton.getInstance("xiaohong");
console.log(instance1.getName()); // 打印 xiaoming
console.log(instance2.getName()); // 打印 xiaoming
通过 Singleton.getInstance 来获取 Singleton 类的唯一对象,当第一次创建时,创建一个新的 Singleton 实例并且保存在内部,当第二次创建时,发现已存在实例直接返回之前创建好的实例,实现单例模式。
我们也可以使用代理类的方式来实现单例模式,稍微改动一下代码:
var Singleton = function (name) {
this.name = name;
}
Singleton.prototype.getName = function () {
return this.name;
}
// 引入代理类
var ProxySingleton = (function () {
var instance = null;
return function (name) {
if(instance == null) {
instance = new Singleton(name);
}
return instance;
}
})();
var instance1 = new ProxySingleton("xiaoming");
var instance2 = new ProxySingleton("xiaohong");
console.log(instance1.getName()); // 打印 xiaoming
console.log(instance2.getName()); // 打印 xiaoming
虽然通过传统方法可以实现单例模式,但是 JavaScript 其实是不存在类的,也正因为如此,传统的单例模式实现在 JavaScript 中并不适用。但在 JavaScript 中,我们经常会把全局变量当成单例来使用。例如:
var a = {};
当用这种方式创建对象 a 时,对象 a 确实是独一无二的。如果 a变量被声明在全局作用域下,则我们可以在代码中的任何位置使用这个变量,全局变量提供给全局访问是理所当然的。这样就满足了单例模式的两个条件(1.只能有一个实例;2.提供一个访问它的全局访问点)。
前面我们已经知道了两种方式实现单例模式,接下来我们再了解另外一种----惰性单例。也就是在我们需要的时候才创建对象实例。
例如我们要开发一个登录浮窗,这个浮窗在页面是要唯一的,不能同时出现两个登录浮窗,我们可以用一个变量来判断是否已经创建过登录浮窗,来实现浮窗唯一。代码示例:
var createLoginLayer = (function () {
var div = null;
return function () {
if(div == null) {
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';
};
这段代码虽然实现了惰性单例,但是需求总是会增加的,如果我们要在页面增加一些其它标签的内容,比如 iframe,这时我们又要需要重新写一遍上面的代码:
var createIframe = (function () {
var iframe= null;
return function () {
if(iframe== null) {
iframe= document.createElement('iframe');
iframe.innerHTML = '我是登录浮窗';
iframe.style.dsiplay = 'none';
document.body.appendChild(iframe);
}
return iframe;
}
})();
这就违反了单一职责原则,把创建对象和管理单例的逻辑都放在了 createLoginLayer 对象内部。我们需要把不变的部分抽离出来,我们可以发现管理单例的逻辑始终都是一样的,可以把这部分代码抽象出来:
var obj;
return function(){
if( obj == null ) {
obj = xxx; // 不相同部分
}
return obj;
}
然后抽象出一个专门只为创建单一实例的一个函数:
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('loginBtn').onclick = function() {
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block';
};
增加 iframe 或者其它元素跟此方法一样,只需重新写创建对应的页面元素的函数,然后调用 getSingle 创建对应的页面内容即可。