前言
最近读了《JavaScript设计模式与开发实践》这本书,感觉收获很大。便试着写一下记录总结,希望也能对大家有帮助。本文所有代码默认为JavaScript。
说明
单例模式是指:保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式在前端开发中出现的频率其实还是挺高的。
实现例子
一般情况下,我们在使用某个实例的时候,都会先new一个实例出来。
// 例如我们现在需要获得用户信息
const user = new User();
const userInfo = user.getInfo();
这种情况下,每次我们想要获取用户信息的时候,都需要先new一个user实例。但在同一个应用下,用户应该是唯一的(每次在同一浏览器标签页,打开页面应用的用户应该有且只有一个人)。因此我们通常会用下列方式优化一下代码。
全局变量
window.user = new User();
// 每次获取用户信息,只要调用全局变量user的接口即可。
const userInfo = user.getInfo();
用函数确保获得的是同一个实例
改用函数获取实例的好处是,当我们用new的时候一定会创建一个实例。但用函数的话,我们可以自己在函数中控制返回的内容
const User = function(name,age){
this.name = name;
this.age = age;
this.instance = null;
}
User.prototype.getInstance = function(name,age){
if(!this.instance){
this.instance = new User(name,age);
}
return instance;
}
User.prototype.getInfo = function(){
return {
name:this.name,
age:this.age
};
}
// 获取用户实例,这里甚至可以修改信息。
var tom = User.getInstance(tom,23);
var john = User.getInstance(john,21);
// 确认是同一个实例
console.log(tom === john);
优化:用代理实现
上述的实现,确实是符合单例模式,但同时也存在一个问题,我们在创建一个类的时候,未必可以确认这个类将来是单例还是多例。为了保证这个类的“纯粹”。我们可以用代理的方式来实现单例模式。
const User = function(name,age){
this.name = name;
this.age = age;
}
User.prototype.getInfo = function(){
return {
name:this.name,
age:this.age
};
}
// 代理做的事就是上方getInstance做的事,只是通过函数代理之后,可以让类和这个获取单例的动作更区分。
const ProxyCreateUser() = function(name,age){
let instance;
return function (name,age){
if(!instance){
instance = new User(name,age);
}
return instance;
}
}
惰性单例
惰性单例是指,在需要的时候才创建对象实例,可以认为是一种优化方案。像上面提到的用window挂载全局变量的方式,就是一开始这个实例就创建好的。用函数的方式,就可以实现在需要时再创建的惰性单例。
实际运用例子
在dom中运用
在页面dom中,我们会有一些像弹窗这样的组件。如登录弹窗,在用户的操作需要用户权限的时候,如果判断该用户没有登录。我们可以弹出登录弹窗,如果这个弹窗不存在,我们就创建。如果已经创建过了,我们就直接让他显示,登录操作完成之后隐藏。
var loginLayer = (function(){
var div = document.createElement('div');
div.innerHtml = '登录窗';
div.style.display = 'none';
document.body.appendChild(div);
return div;
})()
// 点击按钮跳出弹窗
const btn = document.getElementById('loginBtn');
btn.onclick= function(){
loginLayer.style.display = 'block';
}
在js中运用
在js代码中,我们时常会用到像websocket,ajax请求等,但我们会对他们做封装。如果每次在用时,我们再创建一个请求实例,再做自定义操作就会提高运行成本。这时我们就可以把他们做成一个单例,每次直接使用即可。举个例子:假如我们在使用axios发送请求的时候,同一项目通常会有一些共同的配置。
// 正常请求
axios({
url:'xxxxx',
// 项目要求统一timeout设置为1000
timeout: 1000,
});
当我们在确认这timeout配置是固定统一的时候,我们可以用axiso提供的create方法创建一个新的实例。
const myAxios = axios.create({
baseURL: 'xxxx',
timeout: 1000
});
// 接下来请求都直接用myAxios
myAxios({
url:'xxxxx',
//....
});
假如我们在多个开发者同时开发的时候,可以通过共识让大家都创建一个myAxios来发送请求。但这样显然不是最好的处理方式。因为当axios实例需要修改的时候,需要各个开发者同时修改自己的代码。所以这个实例最好应该写在同一个地方,统一维护。
// myAxiso.js
const myAxios = axios.create({
baseURL: 'xxxx',
timeout: 1000
});
export default myAxios;
这样其他的开发者只有引用这个文件输出的实例就能直接发送请求。并且后续只有维护这个文件就行。
总结
单例模式在JavaScript中可以说是一种最常用的设计模式,但由于语言设计的关系,我们可能不太感知到。但作为一种优化思想,大家可以思考一下在目前的开发项目中,有没有内容是可以通过单例方式实现性能或者代码优化的?
参考
《JavaScript设计模式与开发实践》—— 曾探