单例模式
单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。
———来自维基百科
一个很典型的应用是在点击登录按钮,弹出登录浮窗,不论点击多少次登录按钮,页面始终只会弹出一个登录浮窗。
实现单例模式
思路很简单,用一个变量记录是否已经为某个类创建过对象,如果没有则返回新建的对象,反之则返回之前创建的对象。
在构造器里记录实例:
var Singleton = function(name) {
this.name = name;
this.instance = null;
};
Singleton.prototype.getName = function() {
console.log(this.name);
};
Singleton.getInstance = function(name) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
};
var a = Singleton.getInstance('sean1');
var b = Singleton.getInstance('sean2');
console.log(a === b); // true
使用闭包记录实例:
var Singleton = function(name) {
this.name = name;
};
Singleton.prototype.getName = function() {
console.log(this.name);
};
Singleton.getInstance = (function() {
var instance = null;
return function(name) {
if (!instance) {
instance = new Singleton(name);
}
return instance;
};
})();
var a = Singleton.getInstance('sean1');
var b = Singleton.getInstance('sean2');
console.log(a === b); // true
以上方法相对简单,但是Singleton
类的使用者必须知道这是一个单例类,需要调用getInstance()
函数(而不是new
的方式)来获取对象,这也就是增加了“不透明性”。
透明的单例模式
利用一个IIFE
形成一个闭包,在里边通过变量instance
来记录实例,并返回构造函数。
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('sean1');
var b = new CreateDiv('sean2');
console.log(a === b); // true
上面完成了一个透明的单例类的编写,但还是有缺点,增加了一些程序的复杂度,且阅读性差。还有重要的一点是违反了“单一职责原则”,接下来再改进一下。
用代理实现单例模式
其实就是将实际的业务代码与负责管理单例的代码分离,管理单例的类就是代理类。
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 a1 = new ProxySingletonCreateDiv('sean1');
var b1 = new ProxySingletonCreateDiv('sean2');
console.log(a1 === b1); // true
// 如果想要创建多个 div 就直接调用 CreateDiv 咯
var a2 = new CreateDiv('sean1');
var b2 = new CreateDiv('sean2');
console.log(a2 === b2); // false
JavaScript 中的单例模式
前面提到的几种实现,更多的是接近传统面向对象语言中的实现,但 JavaScript 是一门无类(class-free)语言,我们只要记住单例模式但核心是 确保只有一个实例,并提供全局访问。
全局变量不是单例模式,但它却满足单例的条件,所以我们经常会把全局变量当成单例来使用:var a = {};
。但是我们都知道全局变量很容易造成命名空间污染、容易被不小心覆盖等问题。我们有必要尽量减少全局变量的使用,即使要,也要将污染降到最低:
-
使用命名空间
var namespace1 = { a: function() { return 1; }, b: function() { return 2; } }; // 或者动态创建命名空间: var MyApp = {}; MyApp.namespace = function(name) { var parts = name.split('.'); var current = MyApp; for (var item of parts) { if (!current[item]) { current[item] = {}; } current = current[item]; } }; MyApp.namespace('event'); MyApp.namespace('dom.style'); console.dir(MyApp);
-
使用闭包封装私有变量
var user = (function() { var __name = 'sean', __age = 19; return { getUserInfo: function() { return __name + '-' + __age; } }; })();
惰性单例
所谓惰性,就是只有在需要的时候才会去做。前面提到的也有满足惰性的实现,不过是基于“类”的。下面来看一下文章开头提到的那个典型的应用实现:
<html>
<body>
<button id="loginBtn">登录</button>
<script>
var createLoginLayer = (function() {
var div;
return function() {
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>
</body>
</html>
上面的代码完成了惰性单例的实现,但仍然违反了单一职责原则,如果我们下次不是创建一个登录浮窗而是别的元素呢,请往后看通用的惰性单例。
通用的惰性单例
// 定义一个管理单例的函数:
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').onclick = function() {
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。