JavaScript设计模式二(单例模式)
这边文章主要是JavaScript中的单例模式
定义:
保证一个类仅有一个实例,并提供一个访问它的全局访问点
其实我们的日常开发中或多或少的用到了单例模式的方法。例如我们做Electron开发的过程中,点击一个按钮创建了一个窗口,后续点击的时候,如果窗口已经存在了就focus窗口,否则创建;或者我们经常会创建一个定时任务,同时把定时任务赋值给一个变量,如果变量不存在就创建,否则不创建。但是大多数情况我们都是利用的是一个变量来控制的。接下来我们看看代码的实现
实现单例模式
其实上面的介绍我们已经说了实现单例模式的思路,就是通过一个变量来控制
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;
}
//这种写法借助了this.instance,其实可以不需要
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('a');
var b = Singleton.getInstance('b');
a===b
这种方式实现了我们说的单例模式,但是有一个很明显的缺点,就是我们实例化的时候不是使用的new方法来实例化,而是用的getInstance方法,也就是说我们必须知道这个类是单例类,才能这样去用,这就增加了不透明性。
透明的单例模式
所谓的透明的单例模式,就是我们可以像正常的类那样去new一个单例类。
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('div');
var b = new CreateDiv('div');
a===b
之所以能够通过new来创建一个单例类的实例,是因为CreateDiv
的返回值是一个构造函数,这个构造函数做了两件事情
- 创建对象和执行init方法
- 保证只有一个对象
我们可以想象如果需求变成了,我们需要CreateInput之类,是不是一直要修改这个类呢?
代理实现单例模式
利用代理就可以很好的解决上面的问题
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 a = new ProxySingletonCreateDiv('div');
var b = new ProxySingletonCreateDiv('div');
利用代理类,我们遵循了单一职责的原则,让代理类负责单例的逻辑,CreateDiv变成一个普通的创建html的类,两者结合达到单例模式的效果
JavaScript中的单例模式
JavaScript单例模式的核心是:
确保只有一个实例,并提供全局访问
与传统的面向对象语言不一样,JavaScript可以定义全局变量,而且我们通常认为全局变量就是一个单例,但是这种使用方式很容易造成命名空间的污染,针对这种问题有两种办法
- 使用命名空间
最简单的就是使用字面量常量:
var namespace1 = {
a: function(){},
b: 'bbbb'
}
- 利用闭包封装变量
var user = (function(){
var _user = 'hahaha';
return {
setUserName: function(name) {
_user = name;
},
getUserName: function(){
console.log(_user);
}
}
})();
惰性单例
定义:
惰性单例是指需要时才创建的单例
上面的那几种方法实际上就是惰性单例,但是那些事面向对象的,我们看看JavaScript中的惰性单例,看一段PC版的代码吧
let historyWindow = null;
ipc.on(cfg.CHANNEL.LOCAL.CHAT.SEARCH_HISTORY, function(event, arg) {
if (!historyWindow) {
historyWindow = new BrowserWindow({
width: 621,
height: 540,
minWidth: 621,
minHeight: 540,
frame: false,
show: false,
});
historyWindow.setAutoHideMenuBar(false);
historyWindow.loadURL('file://' + __dirname + '/../../../client/views/history.html');
if (process.env.NODE_ENV == 'dev') {
historyWindow.webContents.openDevTools();
}
historyWindow.on('close', function() {
historyWindow.webContents.closeDevTools();
});
historyWindow.on('closed', function() {
glb.set(cfg.GLB.HISTORY_WINDOW, null);
glb.remove(cfg.GLB.ADD_MEMBER_WINDOW);
historyWindow = null;
});
historyWindow.webContents.on('did-finish-load', function() {
glb.set(cfg.GLB.HISTORY_WINDOW, historyWindow);
historyWindow.show();
historyWindow.focus();
historyWindow.webContents.send(cfg.CHANNEL.LOCAL.CHAT.OPEN_HISTORY_WINDOW_RECV, arg);
});
} else {
historyWindow.webContents.send(cfg.CHANNEL.LOCAL.CHAT.OPEN_HISTORY_WINDOW_RECV, arg);
if (historyWindow.isMinimized()) {
historyWindow.show();
} else {
historyWindow.focus();
}
}
});
这段代码的逻辑是,点击历史消息的icon,创建一个历史消息的弹出框,后面如果继续点击,就把之前的弹出框focus。
这里其实还有优化的空间,我们知道electron创建一个新的BrowserWindow是很慢的,所以我们创建一次之后,用户点击关闭,其实可以隐藏起来,并不是实际的关闭,这样当用户点击第二次的时候就省略了创建窗口的过程,直接渲染数据就可以。