JS设计模式之单体模式

单体模式的思想在于保证一个特定类仅有一个实例。这意味着当您第二次使用同一个类床创建新对象的时候,应该得到与第一次创建对象完全相同对象。

ES5之前

在ES5之前是没有类这种概念,只有对象。当创建一个新对象时,实际上没有其他对象与其类似,因此新对象已经是单体了。使用对象字面量创建一个简单的对象也是一个单体的例子:

1
2
3
var obj = {
myprop: 'my value'
}

在JS中,对象之间永远不会完全相等,除非它们是同一个对象,因此即使创建一个具有完全相同成员的同类对象,它也不会与第一个对象完全相等。

1
2
3
4
5
var obj2 = {
myprop: 'my value'
}
obj1 === obj2; // false
obj1 == obj2; // false

因此可以认为每次在使用对象字面量创建对象的时候,实际上就正在创建一个单体,并且并不涉及任何特殊语法。

new操作符

使用new可以创建对象,有时可能需要使用此方法的单体实现。这种思想在于当使用同一个构造函数以new操作符来创建多个对象时,应该仅获得指向完全相同的对象的新指针。

1
2
3
var uni = new Universe()
var uni2 = new Universe()
uni1 === uni2 // true

怎么实现上面的单体模式呢?

可能会想到使用缓存该对象实例this,以便当第二次调用该构造函数时能够创建并返回同一个对象。实现方式有多种:

  • 使用全局变量来缓存;但是不推荐,一般原则下,全局变量是有缺点的。
  • 可以在构造函数的静态属性中缓存该变量。唯一缺点是函数属性是公开访问的属性,在外部代码中可能会修改该属性,以至于让您丢失了该实例。
  • 将实例包装在闭包中。这样可以保证该实例的私有性并且保证该实例不会被构造函数之外的代码所修改。代价是带来了额外的闭包开销。

第二种实现

1
2
3
4
5
6
7
8
9
10
11
12
function Universe() {
if(typeof Universe.instance === 'object') {
return Universe.instance
}
this.start_time = 0;
this.bang = "big";
Universe.instance = this;
//return this;
}

第三种实现

1
2
3
4
5
6
7
8
9
10
function Universe() {
var instance = this;
this.start_time = 0;
this.bang = "big";
Universe = function() {
return instance;
}
}

这种实现有一些缺点,在于重写函数时会丢失所有在初始定义和重定义时刻之间添加到它里面的属性;在这里的特定情况下,任何添加到Universe()的原型中的对象都不会存在指向由原始实现所创建实例的活动链接。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
Universe.prototype.nothing = true;
var uni = new Universe()
Universe.prototype.everything = true;
var uni2 = new Universe();
uni.nothing // true
uni2.nothing // true
uni.everything // undefined
uni2.everything // undefined
uni.constructor.name // Universe
uni.constructor === Universe; // false

改进1

可以把第一次就返回改写Universe函数调用得到的实例,并链接原型链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Universe() {
var instance;
Universe = function() {
return instance;
}
Universe.prototype = this;
instance = new Universe();
instance.constuctor = Universe;
instance.start_time = 0;
instance.bang = "big";
return instance;
}

测试得到预期结果

1
2
3
4
5
6
7
8
9
10
11
12
13
Universe.prototype.nothing = true;
var uni = new Universe()
Universe.prototype.everything = true;
var uni2 = new Universe();
uni.nothing // true
uni2.nothing // true
uni.everything //true
uni2.everything // true
uni.constructor.name // Universe
uni.constructor === Universe; // true

改进2-立即执行函数

利用立即执行函数进行改写Universe,并在闭包里面封装一个实例,每次调用构造函数都返回这个实例;

1
2
3
4
5
6
7
8
9
10
11
12
13
var Universe;
(function() {
var instance;
Universe = function() {
if(instance) {
return instance
}
instance = this;
this.start_time = 0;
this.bang = 'Big';
}
}())

ES6实现方法

ES6实现原理其实跟ES5一样,只不过语法不一样而与;

1
2
3
4
5
6
7
8
9
10
class Universe {
constructor() {
if(!Universe.instance) {
this.start_time = 0;
this.bang = "Big";
Universe.instance = this;
}
return Universe.instance;
}
}