1
+ <a name =" a1 " ></a >
1
2
# 对象创建模式
2
3
3
4
在JavaScript中创建对象很容易——可以通过使用对象直接量或者构造函数。本章将在此基础上介绍一些常用的对象创建模式。
@@ -6,6 +7,7 @@ JavaScript语言本身简单、直观,通常也没有其他语言那样的语
6
7
7
8
我们将对命名空间、依赖声明、模块模式以及沙箱模式进行初探——它们帮助更好地组织应用程序的代码,有效地减轻全局污染的问题。除此之外,还会对包括:私有和特权成员、静态和私有静态成员、对象常量、链以及类式函数定义方式在内的话题进行讨论。
8
9
10
+ <a name =" a2 " ></a >
9
11
## 命名空间模式(Namespace Pattern)
10
12
11
13
命名空间可以帮助减少全局变量的数量,与此同时,还能有效地避免命名冲突、名称前缀的滥用。
@@ -59,6 +61,7 @@ JavaScript默认语法并不支持命名空间,但很容易可以实现此特
59
61
本章后续要介绍的沙箱模式则可以避免这些缺点。
60
62
61
63
64
+ <a name =" a3 " ></a >
62
65
###通用命名空间函数
63
66
64
67
随着程序复杂度的提高,代码会分置在不同的文件中以特定顺序来加载,这样一来,就不能保证你的代码一定是第一个申明命名空间或者改变量下的属性的。甚至还会发生属性覆盖的问题。所以,在创建命名空间或者添加属性的时候,最好先检查下是否存在,如下所示:
@@ -125,6 +128,7 @@ JavaScript默认语法并不支持命名空间,但很容易可以实现此特
125
128
126
129
图5-1 MYAPP命名空间在Firebug下的可视结果
127
130
131
+ <a name =" a4 " ></a >
128
132
## 声明依赖
129
133
130
134
JavaScript库往往是模块化而且有用到命名空间的,这使用你可以只使用你需要的模块。比如在YUI2中,全局变量YAHOO就是一个命名空间,各个模块作为全局变量的属性,比如YAHOO.util.Dom(DOM模块)、YAHOO.util.Event(事件模块)。
@@ -173,6 +177,7 @@ JavaScript库往往是模块化而且有用到命名空间的,这使用你可
173
177
*/
174
178
175
179
180
+ <a name =" a5 " ></a >
176
181
## 私有属性和方法
177
182
178
183
JavaScript不像Java或者其它语言,它没有专门的提供私有、保护、公有属性和方法的语法。所有的对象成员都是公有的:
@@ -196,6 +201,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护
196
201
var toy = new Gadget();
197
202
console.log(toy.name); // `name` is public console.log(toy.stretch()); // stretch() is public
198
203
204
+ <a name =" a6 " ></a >
199
205
### 私有成员
200
206
201
207
尽管语言并没有用于私有成员的专门语法,但你可以通过闭包来实现。在构造函数中创建一个闭包,任何在这个闭包中的部分都不会暴露到构造函数之外。但是,这些私有变量却可以被公有方法访问,也就是在构造函数中定义的并且作为返回对象一部分的那些方法。我们来看一个例子,name是一个私有成员,在构造函数之外不能被访问:
@@ -217,12 +223,14 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护
217
223
218
224
如你所见,在JavaScript创建私有成员很容易。你需要做的只是将私有成员放在一个函数中,保证它是函数的本地变量,也就是说让它在函数之外不可以被访问。
219
225
226
+ <a name =" a7 " ></a >
220
227
### 特权方法
221
228
222
229
特权方法的概念不涉及到任何语法,它只是一个给可以访问到私有成员的公有方法的名字(就像它们有更多权限一样)。
223
230
224
231
在前面的例子中,getName()就是一个特权方法,因为它有访问name属性的特殊权限。
225
232
233
+ <a name =" a8 " ></a >
226
234
### 私有成员失效
227
235
228
236
当你使用私有成员时,需要考虑一些极端情况:
@@ -266,6 +274,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护
266
274
267
275
当你需要传递所有的数据时,有另外一种方法,就是使用通用的对象复制函数创建specs对象的一个副本。下一章提供了两个这样的函数——一个叫extend(),它会浅复制一个给定的对象(只复制顶层的成员)。另一个叫extendDeep(),它会做深复制,遍历所有的属性和嵌套的属性。
268
276
277
+ <a name =" a9 " ></a >
269
278
### 对象字面量和私有成员
270
279
271
280
到目前为止,我们只看了使用构建函数创建私有成员的示例。如果使用对象字面量创建对象时会是什么情况呢?是否有可能含有私有成员?
@@ -307,6 +316,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护
307
316
308
317
这个例子也是所谓的“模块模式”的基础,我们稍后将讲到它。
309
318
319
+ <a name =" a10 " ></a >
310
320
### 原型和私有成员
311
321
312
322
使用构造函数创建私有成员的一个弊端是,每一次调用构造函数创建对象时这些私有成员都会被创建一次。
@@ -338,6 +348,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护
338
348
var toy = new Gadget();
339
349
console.log(toy.getName()); // privileged "own" method console.log(toy.getBrowser()); // privileged prototype method
340
350
351
+ <a name =" a11 " ></a >
341
352
### 将私有函数暴露为公有方法
342
353
343
354
“暴露模式”是指将已经有的私有函数暴露为公有方法。当对对象进行操作时,所有功能代码都对这些操作很敏感,而你想尽量保护这些代码的时候很有用。(译注:指对来自外部的修改很敏感。)但同时,你又希望能提供一些功能的访问权限,因为它们会被用到。如果你把这些方法公开,就会使得它们不再健壮,你的API的使用者可能修改它们。在ECMAScript5中,你可以选择冻结一个对象,但在之前的版本中不可用。下面进入暴露模式(原来是由Christian Heilmann创造的模式,叫“暴露模块模式”)。
@@ -386,6 +397,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护
386
397
myarray.indexOf = null;
387
398
myarray.inArray(["a", "b", "z"], "z"); // 2
388
399
400
+ <a name =" a12 " ></a >
389
401
## 模块模式
390
402
391
403
模块模式使用得很广泛,因为它可以为代码提供特定的结构,帮助组织日益增长的代码。不像其它语言,JavaScript没有专门的“包”(package)的语法,但模块模式提供了用于创建独立解耦的代码片段的工具,这些代码可以被当成黑盒,当你正在写的软件需求发生变化时,这些代码可以被添加、替换、移除。
@@ -462,6 +474,7 @@ MYAPP.utilities.array = (function () {
462
474
463
475
模块模式被广泛使用,这是一种值得强烈推荐的模式,它可以帮助组织代码,尤其是代码量在不断增长的时候。
464
476
477
+ <a name =" a13 " ></a >
465
478
### 暴露模块模式
466
479
467
480
我们在本章中讨论私有成员模式时已经讨论过暴露模式。模块模式也可以用类似的方法来组织,将所有的方法保持私有,只在最后暴露需要使用的方法来初始化API。
@@ -495,6 +508,7 @@ MYAPP.utilities.array = (function () {
495
508
};
496
509
}());
497
510
511
+ <a name =" a14 " ></a >
498
512
### 创建构造函数的模块
499
513
500
514
前面的例子创建了一个对象` MYAPP.utilities.array ` ,但有时候使用构造函数来创建对象会更方便。你也可以同样使用模块模式来做。唯一的区别是包裹模块的立即执行的函数会在最后返回一个函数,而不是一个对象。
@@ -542,6 +556,7 @@ MYAPP.utilities.array = (function () {
542
556
543
557
var arr = new MYAPP.utilities.Array(obj);
544
558
559
+ <a name =" a15 " ></a >
545
560
### 在模块中引入全局上下文
546
561
547
562
作为这种模式的一个常见的变种,你可以给包裹模块的立即执行的函数传递参数。你可以传递任何值,但通常会传递全局变量甚至是全局对象本身。引入全局上下文可以加快函数内部的全局变量的解析,因为引入之后会作为函数的本地变量:
@@ -554,6 +569,7 @@ MYAPP.utilities.array = (function () {
554
569
555
570
}(MYAPP, this));
556
571
572
+ <a name =" a16 " ></a >
557
573
## 沙箱模式
558
574
559
575
沙箱模式主要着眼于命名空间模式的短处,即:
@@ -565,6 +581,7 @@ MYAPP.utilities.array = (function () {
565
581
566
582
这个模式在YUI3中用得很多,但是需要记住的是,下面的讨论只是一些示例实现,并不讨论YUI3中的消息箱是如何实现的。
567
583
584
+ <a name =" a17 " ></a >
568
585
### 全局构造函数
569
586
570
587
在命名空间模式中 ,有一个全局对象,而在沙箱模式中,唯一的全局变量是一个构造函数,我们把它命名为` Sandbox() ` 。我们使用这个构造函数来创建对象,同时也要传入一个回调函数,这个函数会成为代码运行的独立空间。
@@ -633,6 +650,7 @@ MYAPP.utilities.array = (function () {
633
650
634
651
现在我们来看一下如何实现` Sandbox() ` 构造函数和它的模块来支持上面讲到的所有功能。
635
652
653
+ <a name =" a18 " ></a >
636
654
### 添加模块
637
655
638
656
在动手实现构造函数之前,我们来看一下如何添加模块。
@@ -663,6 +681,7 @@ MYAPP.utilities.array = (function () {
663
681
664
682
实现每个模块功能的函数接受一个实例` box ` 作为参数,并给这个实例添加属性和方法。
665
683
684
+ <a name =" a19 " ></a >
666
685
### 实现构造函数
667
686
668
687
最后,我们来实现` Sandbox() ` 构造函数(你可能会很自然地想将这类构造函数命名为对你的类库或者应用有意义的名字):
@@ -722,10 +741,12 @@ MYAPP.utilities.array = (function () {
722
741
- 当我们知道依赖的模块之后就初始化它们,也就是调用实现每个模块的函数。
723
742
- 构造函数的最后一个参数是回调函数。这个回调函数会在最后使用新创建的实例来调用。事实上这个回调函数就是用户的沙箱,它被传入一个` box ` 对象,这个对象包含了所有依赖的功能。
724
743
744
+ <a name =" a20 " ></a >
725
745
## 静态成员
726
746
727
747
静态属性和方法是指那些在所有的实例中保持一致的成员。在基于类的语言中,表态成员是用专门的语法来创建,使用时就像是类自己的成员一样。比如` MathUtils ` 类的` max() ` 方法会被像这样调用:` MathUtils.max(3, 5) ` 。这是一个公有静态成员的示例,即可以在不实例化类的情况下使用。同样也可以有私有的静态方法,即对类的使用者不可见,而在类的所有实例间是共享的。我们来看一下如何在JavaScript中实现公有和私有静态成员。
728
748
749
+ <a name =" a21 " ></a >
729
750
### 公有静态成员
730
751
731
752
在JavaScript中没有专门用于静态成员的语法。但通过给构造函数添加属性的方法,可以拥有和基于类的语言一样的使用语法。之所有可以这样做是因为构造函数和其它的函数一样,也是对象,可以拥有属性。前一章讨论过的Memoization模式也使用了同样的方法,即给函数添加属性。
@@ -800,6 +821,7 @@ MYAPP.utilities.array = (function () {
800
821
var a = new Gadget('499.99');
801
822
a.isShiny(); // "you bet, it costs $499.99!"
802
823
824
+ <a name =" a22 " ></a >
803
825
### 私有静态成员
804
826
805
827
到目前为止,我们都只讨论了公有的静态方法,现在我们来看一下如何实现私有静态成员。所谓私有静态成员是指:
@@ -864,6 +886,7 @@ MYAPP.utilities.array = (function () {
864
886
865
887
静态属性(包括私有和公有)有时候会非常方便,它们可以包含和具体实例无关的方法和数据,而不用在每次实例中再创建一次。当我们在第七章中讨论单例模式时,你可以看到使用静态属性实现类式单例构造函数的例子。
866
888
889
+ <a name =" a23 " ></a >
867
890
## 对象常量
868
891
869
892
JavaScript中是没有常量的,尽管在一些比较现代的环境中可能会提供` const ` 来创建常量。
@@ -952,6 +975,7 @@ JavaScript中是没有常量的,尽管在一些比较现代的环境中可能
952
975
// is the value still intact?
953
976
constant.get("maxwidth"); // 480
954
977
978
+ <a name =" a24 " ></a >
955
979
## 链式调用模式
956
980
957
981
使用链式调用模式可以让你在一对个象上连续调用多个方法,不需要将前一个方法的返回值赋给变量,也不需要将多个方法调用分散在多行:
@@ -983,6 +1007,7 @@ JavaScript中是没有常量的,尽管在一些比较现代的环境中可能
983
1007
obj.add(3);
984
1008
obj.shout(); // 5
985
1009
1010
+ <a name =" a25 " ></a >
986
1011
### 链式调用模式的利弊
987
1012
988
1013
使用链式调用模式的一个好处就是可以节省代码量,使得代码更加简洁和易读,读起来就像在读句子一样。
@@ -995,6 +1020,7 @@ JavaScript中是没有常量的,尽管在一些比较现代的环境中可能
995
1020
996
1021
document.getElementsByTagName('head')[0].appendChild(newnode);
997
1022
1023
+ <a name =" a26 " ></a >
998
1024
## method()方法
999
1025
1000
1026
JavaScript对于习惯于用类来思考的人来说可能会比较费解,这也是很多开发者希望将JavaScript代码变得更像基于类的语言的原因。其中的一种尝试就是由Douglas Crockford提出来的` method() ` 方法。其实,他也承认将JavaScript变得像基于类的语言是不推荐的方法,但不管怎样,这都是一种有意思的模式,你可能会在一些应用中见到。
@@ -1044,6 +1070,7 @@ JavaScript对于习惯于用类来思考的人来说可能会比较费解,这
1044
1070
1045
1071
在` method() ` 的实现中,我们首先检查这个方法是否已经被实现过,如果没有则继续,将传入的参数` implementation ` 加到构造函数的原型中。在这里` this ` 指向构造函数,而我们要增加的功能正在在这个构造函数的原型上。
1046
1072
1073
+ <a name =" a27 " ></a >
1047
1074
## 小结
1048
1075
1049
1076
在本章中你看到了好几种除了字面量和构造函数之外的创建对象的方法。
0 commit comments