1
- <a name =" a1 " ></a >
2
1
# 设计模式
3
2
4
3
在GoF(Gang of Four)的书中提出的设计模式为面向对象的软件设计中遇到的一些普遍问题提供了解决方案。它们已经诞生很久了,而且被证实在很多情况下是很有效的。这正是你需要熟悉它的原因,也是我们要讨论它的原因。
5
4
6
5
尽管这些设计模式跟语言和具体的实现方式无关,但它们多年来被关注到的方面仍然主要是在强类型静态语言比如C++和Java中的应用。
7
6
8
- JavaScript作为一种基于原型的弱类型动态语言,使得有些时候实现某些模式时相当简单 ,甚至不费吹灰之力。
7
+ JavaScript作为一种基于原型的弱类型动态语言,有些时候实现某些模式时相当简单 ,甚至不费吹灰之力。
9
8
10
9
让我们从第一个例子——单例模式——来看一下在JavaScript中和静态的基于类的语言有什么不同。
11
10
12
- <a name =" a2 " ></a >
13
11
## 单例
14
12
15
13
单例模式的核心思想是让指定的类只存在唯一一个实例。这意味着当你第二次使用相同的类去创建对象的时候,你得到的应该和第一次创建的是同一个对象。
@@ -28,143 +26,138 @@ JavaScript作为一种基于原型的弱类型动态语言,使得有些时候
28
26
obj === obj2; // false
29
27
obj == obj2; // false
30
28
31
- 所以你可以说当你每次使用对象字面量创建一个对象的时候就是在创建一个单例,并没有特别的语法迁涉进来 。
29
+ 所以你可以说当你每次使用对象字面量创建一个对象的时候就是在创建一个单例,并没有什么特别的语法牵涉进来 。
32
30
33
- > 需要注意的是,有的时候当人们在JavaScript中提出“单例”的时候,它们可能是在指第5章讨论过的 “模块模式”。
31
+ > 需要注意的是,有的时候当人们在JavaScript中提出“单例”的时候,它们可能是在指第五章讨论过的 “模块模式”。
34
32
35
- <a name =" a3 " ></a >
36
33
### 使用new
37
34
38
- JavaScript没有类,所以一字一句地说单例的定义并没有什么意义。但是JavaScript有使用new 、通过构造函数来创建对象的语法,有时候你可能需要这种语法下的一个单例实现。这也就是说当你使用new 、通过同一个构造函数来创建多个对象的时候,你应该只是得到同一个对象的不同引用。
35
+ JavaScript没有类,所以一字一句地说单例的定义并没有什么意义。但是JavaScript有使用 ` new ` 、通过构造函数来创建对象的语法,有时候你可能需要这种语法下的一个单例实现。这也就是说当你使用 ` new ` 、通过同一个构造函数来创建多个对象的时候,你应该只是得到同一个对象的不同引用。
39
36
40
- > 温馨提示:从一个实用模式的角度来说,下面的讨论并不是那么有用,只是更多地在实践模拟一些语言中关于这个模式的一些问题的解决方案 。这些语言主要是(静态强类型的)基于类的语言,在这些语言中,函数并不是“一等公民”。
37
+ > 温馨提示:从一个实用模式的角度来说,下面的讨论并不是那么有用,只是更多地在模拟一些语言中关于这个模式的一些问题的解决方案 。这些语言主要是(静态强类型的)基于类的语言,在这些语言中,函数并不是“一等公民”。
41
38
42
39
下面的代码片段展示了期望的结果(假设你忽略了多元宇宙的设想,接受了只有一个宇宙的观点):
43
40
44
41
var uni = new Universe();
45
42
var uni2 = new Universe();
46
43
uni === uni2; // true
47
44
48
- 在这个例子中,uni只在构造函数第一次被调用时创建 。第二次(以及后续更多次)调用时,同一个uni对象被返回。这就是为什么uni === uni2的原因 ——因为它们实际上是同一个对象的两个引用。那么怎么在JavaScript达到这个效果呢?
45
+ 在这个例子中,` uni ` 只在构造函数第一次被调用时创建 。第二次(以及后续更多次)调用时,同一个 ` uni ` 对象被返回。这就是为什么 ` uni === uni2 ` 的原因 ——因为它们实际上是同一个对象的两个引用。那么怎么在JavaScript达到这个效果呢?
49
46
50
- 当对象实例this被创建时,你需要在Universe构造函数中缓存它 ,以便在第二次调用的时候返回。有几种选择可以达到这种效果:
47
+ 当对象实例 ` this ` 被创建时,你需要在 ` Universe() ` 构造函数中缓存它 ,以便在第二次调用的时候返回。有几种选择可以达到这种效果:
51
48
52
49
- 你可以使用一个全局变量来存储实例。不推荐使用这种方法,因为通常我们认为使用全局变量是不好的。而且,任何人都可以改写全局变量的值,甚至可能是无意中改写。所以我们不再讨论这种方案。
53
- - 你也可以将对象实例缓存在构造函数的属性中。在JavaScript中,函数也是对象,所以它们也可以有属性。你可以写一些类似Universe.instance的属性来缓存对象 。这是一种漂亮干净的解决方案,不足之处是instance属性仍然是可以被公开访问的 ,别人写的代码可能修改它,这样就会失去这个实例。
50
+ - 你也可以将对象实例缓存在构造函数的属性中。在JavaScript中,函数也是对象,所以它们也可以有属性。你可以写一些类似 ` Universe.instance ` 的属性来缓存对象 。这是一种漂亮干净的解决方案,不足之处是 ` instance ` 属性仍然是可以被公开访问的 ,别人写的代码可能修改它,这样就会失去这个实例。
54
51
- 你可以将实例包裹在闭包中。这可以保持实例是私有的,不会在构造函数之外被修改,代价是一个额外的闭包。
55
52
56
53
让我们来看一下第二种和第三种方案的实现示例。
57
54
58
- <a name =" a4 " ></a >
59
55
### 将实例放到静态属性中
60
56
61
- 下面是一个将唯一的实例放入Universe构造函数的一个静态属性中的例子 :
57
+ 下面是一个将唯一的实例放入 ` Universe() ` 构造函数的一个静态属性中的例子 :
62
58
63
59
function Universe() {
64
60
65
- // do we have an existing instance?
66
- if (typeof Universe.instance === "object") {
67
- return Universe.instance;
68
- }
69
-
70
- // proceed as normal
71
- this.start_time = 0;
72
- this.bang = "Big";
73
-
74
- // cache
75
- Universe.instance = this;
76
-
77
- // implicit return:
78
- // return this;
61
+ // 实例是否已经存在?
62
+ if (typeof Universe.instance === "object") {
63
+ return Universe.instance;
64
+ }
65
+
66
+ // 处理普通逻辑
67
+ this.start_time = 0;
68
+ this.bang = "Big";
69
+
70
+ // 缓存实例
71
+ Universe.instance = this;
72
+
73
+ // 隐式return:
74
+ // return this;
79
75
}
80
76
81
- // testing
77
+ // 测试
82
78
var uni = new Universe();
83
79
var uni2 = new Universe();
84
80
uni === uni2; // true
85
81
86
- 如你所见,这是一种直接有效的解决方案,唯一的缺陷是instance是可被公开访问的 。一般来说它被其它代码误删改的可能是很小的(起码比全局变量instance要小得多 ),但是仍然是有可能的。
82
+ 如你所见,这是一种直接有效的解决方案,唯一的缺陷是 ` instance ` 是可被公开访问的 。一般来说它被其它代码误删改的可能是很小的(起码比全局变量 ` instance ` 要小得多 ),但是仍然是有可能的。
87
83
88
- <a name =" a5 " ></a >
89
84
### 将实例放到闭包中
90
85
91
- 另一种实现基于类的单例模式的方法是使用一个闭包来保护这个唯一的实例。你可以通过第5章讨论过的 “私有静态成员模式”来实现。唯一的秘密就是重写构造函数:
86
+ 另一种实现基于类的单例模式的方法是使用一个闭包来保护这个唯一的实例。你可以通过第五章讨论过的 “私有静态成员模式”来实现。唯一的秘密就是重写构造函数:
92
87
93
88
function Universe() {
94
89
95
- // the cached instance
90
+ // 缓存实例
96
91
var instance = this;
97
92
98
- // proceed as normal
93
+ // 处理普通逻辑
99
94
this.start_time = 0;
100
95
this.bang = "Big";
101
96
102
- // rewrite the constructor
97
+ // 重写构造函数
103
98
Universe = function () {
104
99
return instance;
105
100
};
106
101
}
107
102
108
- // testing
103
+ // 测试
109
104
var uni = new Universe();
110
105
var uni2 = new Universe();
111
106
uni === uni2; // true
112
107
113
- 第一次调用时,原始的构造函数被调用并且正常返回this 。在后续的调用中,被重写的构造函数被调用。被重写怕这个构造函数可以通过闭包访问私有的instance变量并且将它返回 。
108
+ 第一次调用时,原来的构造函数被调用并且正常返回 ` this ` 。在后续的调用中,被重写的构造函数被调用。被重写的这个构造函数可以通过闭包访问私有的 ` instance ` 变量并且将它返回 。
114
109
115
- 这个实现实际上也是第4章讨论的自定义函数的又一个例子 。如我们讨论过的一样,这种模式的缺点是被重写的函数(在这个例子中就是构造函数Universe() )将丢失那些在初始定义和重新定义之间添加的属性。在这个例子中,任何添加到Universe() 的原型上的属性将不会被链接到使用原来的实现创建的实例上。(注:这里的“原来的实现”是指实例是由未被重写的构造函数创建的,而Universe() 则是被重写的构造函数。)
110
+ 这个实现实际上也是第四章讨论的重定义函数的又一个例子 。如我们讨论过的一样,这种模式的缺点是被重写的函数(在这个例子中就是构造函数 ` Universe() ` )将丢失那些在初始定义和重新定义之间添加的属性。在这个例子中,任何添加到 ` Universe() ` 的原型上的属性将不会被链接到使用原来的实现创建的实例上。(注:这里的“原来的实现”是指实例是由未被重写的构造函数创建的,而 ` Universe() ` 则是被重写的构造函数。)
116
111
117
112
下面我们通过一些测试来展示这个问题:
118
113
119
- // adding to the prototype
114
+ // 添加成员到原型
120
115
Universe.prototype.nothing = true;
121
116
122
117
var uni = new Universe();
123
118
124
- // again adding to the prototype
125
- // after the initial object is created
119
+ // 在创建一个对象后再添加成员到原型
126
120
Universe.prototype.everything = true;
127
121
128
122
var uni2 = new Universe();
129
123
130
- Testing:
131
- // only the original prototype was
132
- // linked to the objects
124
+ // 测试:
125
+ // 只有原始的原型被链接到对象上
133
126
uni.nothing; // true
134
127
uni2.nothing; // true
135
128
uni.everything; // undefined
136
129
uni2.everything; // undefined
137
130
138
- // that sounds right:
131
+ // constructor看起来是对的
139
132
uni.constructor.name; // "Universe"
140
133
141
- // but that's odd:
134
+ // 但其实不然
142
135
uni.constructor === Universe; // false
143
136
144
- uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指向原来的构造函数 ,而不是被重新定义的那个。
137
+ ` uni.constructor ` 不再和 ` Universe() ` 相同的原因是 ` uni.constructor ` 仍然是指向原来的构造函数 ,而不是被重新定义的那个。
145
138
146
- 如果一定被要求让prototype和constructor的指向像我们期望的那样 ,可以通过一些调整来做到:
139
+ 如果一定要让 ` prototype ` 和 ` constructor ` 的指向像我们期望的那样 ,可以通过一些调整来做到:
147
140
148
141
function Universe() {
149
142
150
- // the cached instance
143
+ // 缓存实例
151
144
var instance;
152
145
153
- // rewrite the constructor
146
+ // 重写构造函数
154
147
Universe = function Universe() {
155
148
return instance;
156
149
};
157
150
158
- // carry over the prototype properties
151
+ // 重写prototype属性
159
152
Universe.prototype = this;
160
153
161
- // the instance
154
+ // 创建实例
162
155
instance = new Universe();
163
156
164
- // reset the constructor pointer
157
+ // 重写constructor属性
165
158
instance.constructor = Universe;
166
159
167
- // all the functionality
160
+ // 其它的功能代码
168
161
instance.start_time = 0;
169
162
instance.bang = "Big";
170
163
@@ -173,24 +166,23 @@ uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指
173
166
174
167
现在所有的测试结果都可以像我们期望的那样了:
175
168
176
- // update prototype and create instance
169
+ // 修改原型,创建对象
177
170
Universe.prototype.nothing = true; // true
178
171
var uni = new Universe();
179
172
Universe.prototype.everything = true; // true
180
173
var uni2 = new Universe();
181
174
182
- // it's the same single instance
175
+ // 它们是同一个实例
183
176
uni === uni2; // true
184
177
185
- // all prototype properties work
186
- // no matter when they were defined
178
+ // 所有的原型上的属性都正常工作,不管是什么时候在哪添加的
187
179
uni.nothing && uni.everything && uni2.nothing && uni2.everything; // true
188
- // the normal properties work
180
+ // 普通成员也可以正常工作
189
181
uni.bang; // "Big"
190
- // the constructor points correctly
182
+ // constructor指向正确
191
183
uni.constructor === Universe; // true
192
184
193
- 另一种可选的解决方案是将构造函数和实例包在一个立即执行的函数中 。当构造函数第一次被调用的时候,它返回一个对象并且将私有的instance指向它 。在后续调用时,构造函数只是简单地返回这个私有变量。在这种新的实现下,前面所有的测试代码也会和期望的一样:
185
+ 另一种可选的解决方案是将构造函数和实例包在一个即时函数中 。当构造函数第一次被调用的时候,它返回一个对象并且将私有的 ` instance ` 指向它 。在后续调用时,构造函数只是简单地返回这个私有变量。在这种新的实现下,前面所有的测试代码也会和期望的一样:
194
186
195
187
var Universe;
196
188
@@ -206,7 +198,7 @@ uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指
206
198
207
199
instance = this;
208
200
209
- // all the functionality
201
+ // 功能代码
210
202
this.start_time = 0;
211
203
this.bang = "Big";
212
204
0 commit comments