定义:一个类只有一个实例,即使多次实例化该类,也只会返回第一次实例化后的对象。
const 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;
}
let s1 = SingleTon.getInstance('s1');
let s2 = SingleTon.getInstance('s2');
console.log(s1 === s2); //true
//s1 = SingleTon { name: 's1', instance: null }
//s2 = SingleTon { name: 's1', instance: null }
使用闭包改进后的写法
const SingleTon = function(){}
SingleTon.getInstance = (function(){
let instance = null;
return function(){
if(!instance){
instance = new SingleTon();
}
return instance;
}
})()
let s1 = SingleTon.getInstance();
let s2 = SingleTon.getInstance();
console.log(s1 == s2) //true
//s1 == s2 == SingleTon {}
单例模式用途
如果一个类负责连接数据库的线程池、日志记录逻辑等等,此时需要单例模式来保证对象不被重复创建,以达到降低开销的目的。
对于频繁创建,销毁对象,单例模式实现了对象统一,内存只占有一个对象内存分量,在频繁创建中,大大的节约了系统内存。
实例演示
1、登录弹框
常见做法: 实现弹框的一种做法是先创建好弹框, 然后使之隐藏, 这样子的话会浪费部分不必要的 DOM 开销, 我们可以在需要弹框的时候再进行创建, 同时结合单例模式实现只有一个实例, 从而节省部分 DOM 开销。下列为登入框部分代码:
let createLoginBox= function() {
let div = document.createElement('div')
div.innerHTML = '登入弹框'
div.style.display = 'none'
document.body.appendChild(div)
return div;
}
使单例模式和创建弹框代码解耦
let getSingle = function(fn) {
let result;
return function() {
return result || (result = fn.apply(this, arguments));
}
}
let createSingleLoginBox = getSingle(createLoginBox)
document.getElementById('loginBtn').onclick = function() {
let div = createSingleLoginBox();
console.log(div)
}
ElemnetUI2.x就用到了单例模式来重用遮罩
import Vue from 'vue'
import loadingVue from './loading.vue'
const LoadingConstructor = Vue.extend(loadingVue)
let fullscreenLoading
const Loading = (options = {}) => {
if (options.fullscreen && fullscreenLoading) {
return fullscreenLoading
}
let instance = new LoadingConstructor({
el: document.createElement('div'),
data: options
})
if (options.fullscreen) {
fullscreenLoading = instance
}
return instance
}
export default Loading
这里的单例是 fullscreenLoading,是存放在闭包中的,如果用户传的 options 的 fullscreen 为 true 且已经创建了单例的情况下则回直接返回之前创建的单例,如果之前没有创建过,则创建单例并赋值给闭包中的 fullscreenLoading 后返回新创建的单例实例。
这是一个典型的单例模式的应用,通过复用之前创建的全屏蒙层单例,不仅减少了实例化过程,而且避免了蒙层叠加蒙层出现的底色变深的情况。
2、管理模块
.我们在开发中会写许多方法,这里罗列2个做个样子,代码如下
function getId(){
var args = arguments;
if(args.length > 1){
throw new Error('只允许接收一个参数');
}else{
return document.getElementById(args[0]);
}
}
function setHtml(id,text){
document.getElementById(id).innerHTML = text;
}
改进如下
var myFun = {
getId : function(){
var args = arguments;
if(args.length > 1){
throw new Error('只允许接收一个参数');
}else{
return document.getElementById(args[0]);
}
},
setHtml : function(id,text){
this.getId(id).innerHTML = text;
}
}
通过上面的方法,我们解决了2个问题,一个全局变量只有一个,我们可以通过.的形式来获取对应的方法;其次各个方法之间我们可以很方便的用this来进行调用,比起call要直观舒服好多。并且在使用中为了更加好的保护我们的变量,通常比较合理的代码如下所示:
(function(window){
var myFun = {
getId : function(){
var args = arguments;
if(args.length > 1){
throw new Error('只允许接收一个参数');
}else{
return document.getElementById(args[0]);
}
},
setHtml : function(id,text){
this.getId(id).innerHTML = text;
}
}
window.myFun = myFun;
})(window)
我们用一个匿名函数来包裹代码,把全局污染变到最小化。当然单例模式我们更灵活的使用,例如{key1:{},key2:{}}这种嵌套结构来处理逻辑。
单例模式的优缺点
简单分析一下它的优点:
1、单例模式在创建后在内存中只存在一个实例,节约了内存开支和实例化时的性能开支,特别是需要重复使用一个创建开销比较大的类时,比起实例不断地销毁和重新实例化,单例能节约更多资源,比如数据库连接;
2、单例模式可以解决对资源的多重占用,比如写文件操作时,因为只有一个实例,可以避免对一个文件进行同时操作;
3、只使用一个实例,也可以减小垃圾回收机制 GC(Garbage Collecation) 的压力,表现在浏览器中就是系统卡顿减少,操作更流畅,CPU 资源占用更少;
单例模式也是有缺点的
1、单例模式对扩展不友好,一般不容易扩展,因为单例模式一般自行实例化,没有接口;
2、与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化;
单例模式的使用场景
那我们应该在什么场景下使用单例模式呢:
当一个类的实例化过程消耗的资源过多,可以使用单例模式来避免性能浪费;
当项目中需要一个公共的状态,那么需要使用单例模式来保证访问一致性;