javascript 设计模式 - 单例模式

内容大部分来自 《javascript 设计模式与开发实践》一书,适当的结合自己工作实践,做出整理和笔记
系列: javascript 设计模式

单例模式

单例模式的定义是产生一个类的唯一实例。

  • 单例模式只能有一个实例。
  • 对所有的访问都提供这一个单例。

传统语言的单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class BaseSingle {
constructor(...args) {
this.init(args);
}

init() {
// do something
}

static getInstance(...args) {
if (!BaseSingle.$_instance) {
BaseSingle.$_instance = new BaseSingle(args);
}
return BaseSingle.$_instance;
}
}

// 传统语言单例模式的实现
console.log('传统语言的单例模式:', BaseSingle.getInstance('A') === BaseSingle.getInstance('B'))

这样写存在缺点:

  1. 不易于扩展为非单例、
  2. 方法同时处理了两件事,一个保证方法init的执行,另外保证只有一个对象,这违背了单一职责原则

代理单利模式

为了解决上述存在的问题,引入代理单例模式的方法,通过一个代理方法来讲类本身和实现代理的逻辑隔离开来。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 代理单利模式,单例部分抽离出来
class BaseSingle2 {
constructor() {
this.init();
}

init() {
// console.log('ok ...');
}
}

/* 通过闭包 和 return 复杂结构实现 */
const ProxySingleEs5 = (() => {
// 闭包保存变量
let instance;
return function PrxSingle(...args) {
if (!instance) {
instance = new BaseSingle2(args);
}
return instance;
};
})();

console.log('引入代理实现的单例模式:', new ProxySingleEs5() === new ProxySingleEs5());

这里有两个知识点:

  1. 一个是闭包
  2. 一个是在执行new 关键字的时候,如果构造函数返回了一个对象,那么new 会直接返回改对象,否则返回构造函数中的this

JS 中的单例模式

前面提到的是在传统面向对象语言中的实现,单例的对象从类中而来,在js中可以直接把变量当做单例使用。

1
2
3
4
5
6
7
8
9
10
11
const userSingle = (() => {
const name = 'name - xx';
const age = 180;
return {
getUser() {
return `${name} : ${age}`;
}
};
})();

console.log('userSingle:', userSingle.getUser());

通过闭包避免了对全局作用域的污染

JS惰性单例

在需要的时候才创建出对象的实例,这种也是日常开发中最有用的场景。在前面的 传统语言的单例模式 已经使用了惰性,这里换成更适合js语言的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 通用的惰性单例实现
const getSingle = function getSignle(fn) {
let result;
return function proxySingle(...args) {
if (!result) {
result = fn.apply(this, args);
}
return result;
}
}

// 例:创建一个元素,避免多次创建
const createDom = function createDom() {
const domDiv = document.createElement('div');
domDiv.innerHTML = '我创建的DOM 节点';
document.body.appendChild(domDiv);
return domDiv;
};

const singleCreateDom = getSingle(createDom);

console.log('singleCreateDom:', singleCreateDom() === singleCreateDom());

惰性单例模式在实际的开发中有大量的应用

  • 比如在多个模块都依赖一个接口服务,需要但是有不确定调用顺序只用讲接口调用方法单例化
  • 比如需要保证多次创建dom后绑定事件的方法只会执行一次