什么是单例模式
一个构造函数只能有一个实例对象,提供全局访问的功能,如果创建多次,会得到与第一次创建对象完全相同的对象
单例模式的应用
一个实际场景,当我们点击登陆按钮时,页面中可能会出现一个弹框,而这个弹框是唯一的,无论点多少次登陆按钮,弹框只会被创建一次,那么这种情况下就适合用单例模式来创建弹框。
单例的实现方式
1、简单单例模式
通过自执行函数和闭包将instance封装起来。并且返回了真正的Singleton构造方法
var CreateLogin = (function () {
var instance = null;
var CreateLogin = function (html) {
this.html = html;
if (!instance) {
instance = this.init();
}
return instance;
}
CreateLogin.prototype.init = function () {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
return this;
}
return CreateLogin;
})();
上诉方法实现用了两步
- 创建对象,init初始化登录弹窗
- 只返回一个实例对象
存在的问题
- 违背单一职责原则(如果html结构变更)
- init方法在实例中可以调用,在body里添加相应的dom结构
2、代理单例模式
代理模式,自己不做,委托别人做
- 将 createLogin 改写成只负责创建login弹窗的类
- 引入代理类
var CreateLogin = function (html) {
this.html = html;
this.init();
}
CreateLogin.prototype.init = function () {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
var proxySingleton = (function () {
var instance = null;
return function (html) {
return instance || (instance = new CreateLogin(html))
}
})();
var login1 = proxySingleton('login1');
var login2 = proxySingleton('login2');
login1 === login2; // true
login1.init() // 重新创建一个div并加入到body中
还存在init方法的暴露,我们可以对CreateLogin 做进一步的封装
var CreateLogin = (function() {
function CreateLogin(html) {
this.html = html;
_init.call(this);
}
var _init = function () {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
return CreateLogin
})();
var proxySingleton = (function () {
var instance = null;
return function (html) {
return instance || (instance = new CreateLogin(html))
}
})();
var login1 = proxySingleton('login1');
login.init() // 报错
// 扩展proxySingleton,支持其他类,比如注册,类似工厂函数
var proxySingleton = (function () {
var instance = null;
return function () {
var construction = Array.prototype.shift.call(arguments);
var args = Array.prototype.slice.call(arguments);
return instance || (instance = new construction(args))
}
})();
var login1 = proxySingleton(CreateLogin, 'login1');
3、惰性单例模式
惰性单例是指需要的时候才去创建实例,不像简单单例,利用自执行函数加载的时候就创建实例
比如,当打开一个网站时,需要登录,但登陆的弹窗只会在点击登陆按钮时出现,甚至有的网站不需要登录就能直接浏览。这时我们并不需要在页面加载时就去创建一个弹窗。我们大可在需要用的时候去创建。
var createLazyDiv = (function () {
var div = null;
return function () {
if (!div) {
div = document.createElement('div');
div.innerHTML = 'create lazy div';
document.body.appendChild(div);
}
return div;
}
})();
document.getElementById('loginBtn').onclick = function() {
var loginLazy = createLazyDiv();
}
以上我们实现了一个惰性单例的登录。但是我们还是可以把其中的控制只有一个对象的操作抽离出来,让我们来实现一个通用的惰性单例
// 把创建div的方法抽离出来
var createLazyDiv = function (html) {
var div = document.createElement('div');
div.innerHTML = html;
document.body.appendChild(div);
return div;
}
// 把只创建一个实例的方法抽离出来,供其他需要的使用
var getSingleton = (function () {
var instance = null;
return function() {
var fn = Array.prototype.shift.call(arguments);
var args = Array.prototype.slice.call(arguments);
return instance || (instance = fn.apply(this, args));
}
})();
document.getElementById('loginBtn').onclick = function() {
var loginLazy = getSingleton(createLazyDiv, 'login1');
}
到此,我们实现了一个 getSingleton 函数来帮我们实现只有一个实例对象的目的,并且将实例对象要做的指责独立出来,两个方法互不打扰,并可以提供其他需要实现使用惰性单例的需求,方法
ES6实现
上述是ES5的实现方式,现在ES6已经广泛使用,所以实现一套ES6的单例
class Login {
constructor (html) {
this.html = html;
}
static getInstance (html) {
if (!this.instance) {
this.instance = new Login(html)
}
return this.instance
}
}
// 效果
const login1 = Login.getInstance('i am div1')
const login2 = Login.getInstance('i am div2')
console.log(login1 === login2) // true
// 单一职责原则
// 抽离出一份只负责实例对象的方法,实现单例
class Singleton {
constructor () {
if (new.target === Singleton) {
throw new Error('本类不能实例化')
}
}
static getSingleton (html) {
if (!this.instance) {
this.instance = new this(html)
}
return this.instance
}
}
// 继承自单例方法
class Login extends Singleton {
constructor(html) {
super()
this.html = html
}
}
const login1 = Login.getSingleton('i am login1');
const login2 = login.getSingleton('i am login2');
console.log(login1 === login2) // true
后续
在简单模式里,有使用到new 的方式实例化一个对象,那么如果没有使用new 会发生什么? 提到了工厂模式 下一节会介绍下工厂模式,以及其应用