设计模式1-单例模式

设计模式

最近在写Vue插件,发现自己通过直观想法写的东西总是与别人的插件有一定的差距。我也明白别人的库中用了一种或多种设计模式,使代码结构更加恰当等等。之前就说要学习一下设计模式,被搁置到现在,现在买了《JavaScript设计模式与开发实践》,后面会陆续将自己的学习过程通过博客记录。

单例模式

定义

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

描述

单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象。

实现

传统单例模式

JavaScript语言原生设计的时候并没有设计类的概念,并且对象是可以直接通过字面量来新建对象,不一定需要通过类来实例化对象。所以在JavaScript中的单例模式与Java等面向对象语言稍有不同。下面通过传统的面向对象语言的方式来设计单例模式。

简单实现

要实现标准的单例模式,无非是要用一个变量来标志当前是否已经为某个类创建过对象,如果创建过,则在下一次创建的时候直接返回该对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let Singleton = function(name){
this.name = name
}

Singleton.instance = null
Singleton.prototype.getName =function(){
alert(this.name)
}

Singleton.getInstance = function(name){
if(!instance){
this.instance = new Singleton(name)
}
return this.instance
}

let a = Singleton.getInstance('name1')
let b = Singleton.getInstance('name2')

或者使用闭包将instance标志放在闭包中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let Singleton = function(name){
this.name = name
}

Singleton.prototype.getName = function(){
alert(this.name)
}

Singleton.getInstance = (function(){
let instance = null
return function(name){
if(!instance){
instance = new Singleton(name)
}
}
return instance
})()

通过这种方式,我们只能通过getInstance方法来获得该对象。这样增加了该方法的不透明性,不能通过new来创建对象,下面来编写一个头i摩纳哥的单例类。

透明的单例类

下面来实现一个透明类,来实现一个CreateDiv单例类,他的作用是是负责在页面中创建唯一的div节点,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let CreateDiv =(function(){
let instance;
let CreateDiv =function(html){
if(instance){
return instance
}
this.html = html
this.init()
return instance = this
}

CreateDiv.prototype.init = function(){
let div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
return CreateDiv
})()

let a = new CreateDiv('name1')
let b = new CreateDiv('name2')

a === b //true

这个构造方法虽然完成了一个透明的单例类,但是它同样有一些缺点,即违背了“单一职责原理

单一职责原则(SRP:Single responsibility principle)又称单一功能原则,面向对象五个基本原则(SOLID)之一。它规定一个类应该只有一个发生变化的原因。

用代理实现单例模式

为了实现单一职责原理,我们要引入代理类的方式,把负责管理单例的代码分离出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var CreateDiv = function(html){
this.html = html
this.init()
}

CreateDiv.prototype.init = function(){
let div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}

//代理类,可以处理各种类
let proxyCreateDiv = (function(){
let instance
return function(fn, html){
if(!instance){
instance = new fn(html)
}
return instance
}
})()

let a = new ProxySingletonCreateDiv(CreateDiv, 'name1')
let b = new ProxySingletonCreateDiv(CreateDiv, 'name2')

a === b //true

上面的代码中,我们将业务代码与管理逻辑分开到两个类中,实现了单一职责原理

JavaScript中的单例模式

由于JavaScript是一门原生无类(class-free)语言,也是正因如此,生搬硬套单例模式的概念并无意义。

单例模式的核心是:确保只有一个实例,并提供全局访问。

全局变量不是单例模式,但在JavaScript中,我们经常会把全局变量当成单例来使用。

当我们使用字面量对象在全局作用域下创建对象a时,对象a确实是独一无二的,并且也是可以在任意位置上访问。这样满足单例模式的两个条件。

但是这样会污染全局变量,我们可以使用两种方法:

  1. 使用命名空间。
  2. 使用闭包封装私有变量。

惰性单例

所谓惰性单例就是在需要的时候才创建。实际上上面的单例模式就已经是惰性单例了,在为初始化之前都是null,只有在new的时候才进行创建。

下面用一个实际应用场景,就是登录框的设计:在用户未点击之前,登录框不会显示,点击时才会出现登录框。

这里有几种处理方式:

  1. 提前将登录框插入到文档中,并将其display属性设置为none,在点击时将display属性设置为block。这样性能问题就是,这个登录框可能永远用不到,这样就浪费了性能。
  2. 在用户点击时,使用JavaScript将登录加入文档流;在用户登录成功或者×时,将其从文档流中删除。这样的性能问题也有,就是第一次生成的登陆框实际是可以保存,方便以后使用。
  3. 所以最佳方案就是最开始不生成登录框,等用户点击时生成,并且这之后将其display属性设为none,方便以后使用,这样也就是我们的单例模式

所以最后的代码如下(已经将业务代码与管理代码分离):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//获取对应对象单例
let getSingle = function(fn){
let result
//使用一个闭包来保存result
return function(){
return result || (result = fn.apply(this, arguments))
}
}

//创建登录框并返回
let createLoginLayer = function(){
let div = document.createElement('div')
div.innerHTML = '登录框'
document.body.appendChild(div)
return div
}

//创建函数的单例
let createSingleLoginLayer = getSingle(createLoginLayer)

//点击事件监听
document.getElementById('loginBtn').onclick = function(){
let loginLayer = createSingleLoginLayer()
loginLayer.style.display = 'block'
}

//取消事件监听
document.getElementById('cancelBtn').onclick = function(){
let loginLayer = createSingleLoginLayer()
loginLayer.style.display = 'none1
}

总结

单例模式是一个比较重要的单例模式,在进行底层开发或者注重性能的任务中,单例模式可以节省很多消耗,提高性能。

参考

  • 《JavaScript设计模式与开发实践》

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :