Skip to content

AHABHGK

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点

有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象等

1const Singleton = function (name) {
2 this.name = name
3 // this.instance = null
4}
5
6Singleton.prototype.getName = function () {
7 return this.name
8}
9
10// 通过标识 Singleton.instance 属性
11Singleton.getInstance = function (name) {
12 console.log(this) // this 指向 Singleton 构造函数
13 if (!this.instance) {
14 this.instance = new Singleton(name)
15 }
16 return this.instance
17}
18
19// 通过闭包词法作用域标识
20// Singleton.getInstance = (function () {
21// const instance = null
22// return function (name) {
23// if (!instance) {
24// instance = new Singleton(name)
25// }
26// return instance
27// }
28// })()
29
30const a = Singleton.getInstance('hah')
31const b = Singleton.getInstance('aha')
32
33console.log(a === b)
34console.log(a, b)

只能通过 Singleton.getInstance 来获取,不能通过 new,增加“不透明性”

透明的单例模式

1const CreateDiv = (function () {
2 let instance
3
4 const CreateDiv = function (html) {
5 // 1. 保证只有一个 instance
6 if (instance) {
7 return instance
8 }
9 // 2. 创建对象和初始化
10 this.html = html
11 this.init()
12 return instance = this
13 }
14
15 CreateDiv.prototype.init = function () {
16 const div = document.createElement('div)
17 div.innerHTML = this.html
18 document.body.appendChild(div)
19 }
20
21 return CreateDiv
22})()
23
24const a = new CreateDiv('<div>A</div>')
25const b = new CreateDiv('<div>B</div>')
26console.log(a === b)

使用闭包和自执行函数,阅读不方便

CreateDiv 的构造函数实际上负责了两件事情。第一是创建对象和执行初始化init方法,第二是保证只有一个对象。不符合“单一职责原则”

代理实现

对 CreateDiv 进行解藕

1// 创建 div
2const CreateDiv = function (html) {
3 this.html = html
4 this.init()
5}
6
7CreateDiv.prototype.init = function () {
8 const div = document.createElement('div)
9 div.innerHTML = this.html
10 document.body.appendChild(div)
11}
12
13// 保证只有一个 instance 的代理类
14const ProxySingletonCreateDiv = (function () {
15 let instance
16
17 return function (html) {
18 if (!instance) {
19 instance = new CreateDiv(html)
20 }
21 return instance
22 }
23})()
24
25const a = new ProxySingletonCreateDiv('<div>A</div>')
26const b = new ProxySingletonCreateDiv('<div>B</div>')
27console.log(a === b)

JavaScript 中的单例模式

前面提到的几种单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从“类”中创建而来。在以类为中心的语言中,这是很自然的做法

但JavaScript其实是一门无类(class-free)语言,也正因为如此,生搬单例模式的概念并无意义。在JavaScript中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象,为什么要为它先创建一个“类”呢?

可通过命名空间和闭包防止全局变量的污染来创建单例

惰性单例

instance 实例对象总是在我们调用Singleton.getInstance 的时候才被创建,而不是在页面加载好的时候就创建

页面登录弹窗:

1var createLoginLayer = (function(){
2 var div;
3 return function(){
4 if (!div){
5 div = document.createElement( 'div' );
6 div.innerHTML = '我是登录浮窗';
7 div.style.display = 'none';
8 document.body.appendChild( div );
9 }
10
11 return div;
12 }
13})()
14
15document.getElementById('loginBtn').onclick = function(){
16 var loginLayer = createLoginLayer();
17 loginLayer.style.display = 'block';
18}

与第一段代码一样,违反单一职责原则,不能通用于其他标签

通用的惰性单例

先对逻辑进行抽象

抽离管理单例,封装在 getSingle 中,创建对象的具体方法 fn 作为参数传入

1const getSingle = function (fn) {
2 const instance
3 return function (...args) {
4 if (!instance) {
5 instance = fn(...args)
6 }
7 return instance
8 }
9}

进一步简化

1const getSingle = function (fn) {
2 let result
3 return function (...args) {
4 return result || (result = fn(...args))
5 }
6}

页面弹窗:

1const createPopup = function (html) {
2 const div = document.createElement('div')
3 div.innerHTML = html
4 div.style.display = 'none'
5 document.body.appendChild(div)
6 return div
7}
8
9const createSinglePopup = getSingle(createPopup)
10
11document.querySelector('#pupup-btn').addEventListener('click', () => {
12 const popupLayer = createSinglePopup('<div>This is a popup...</div>')
13 popupLayer.style.display = 'block'
14})

这种单例模式的用途远不止创建对象

比如页面中一个列表,渲染完后添加事件委托,之后动态获取数据并最加渲染 li,click 事件只需第一次绑定就好

使用 jQ 的 one:

1var bindEvent = function(){
2 $( 'div' ).one( 'click', function(){
3 alert ( 'click' );
4 });
5};
6
7var render = function(){
8 console.log( '开始渲染列表' );
9 bindEvent();
10 // ...
11}
12
13render()
14render()

使用单例:

1var bindEvent = getSingle(function(){
2 document.getElementById( 'div1' ).onclick = function(){
3 alert ( 'click' );
4 }
5 return true;
6});
7
8var render = function(){
9 console.log( '开始渲染列表' );
10 bindEvent();
11 // ...
12};
13
14render();
15render()

(这个例子有更简单方便的实现,这里只是一个例子)