|
| 1 | +# 对象创建模式 |
| 2 | + |
| 3 | +在JavaScript中创建对象很容易——可以通过使用对象直接量或者构造函数。本章将在此基础上介绍一些常用的对象创建模式。 |
| 4 | + |
| 5 | +JavaScript语言本身简单、直观,通常也没有其他语言那样的语法特性:命名空间、模块、包、私有属性以及静态成员。本章将介绍一些常用的模式,以此实现这些语法特性。 |
| 6 | + |
| 7 | +我们将对命名空间、依赖声明、模块模式以及沙箱模式进行初探——它们帮助更好地组织应用程序的代码,有效地减轻全局污染的问题。除此之外,还会对包括:私有和特权成员、静态和私有静态成员、对象常量、链以及类式函数定义方式在内的话题进行讨论。 |
| 8 | + |
| 9 | +## 命名空间模式(Namespace Pattern) |
| 10 | + |
| 11 | +命名空间可以帮助减少全局变量的数量,与此同时,还能有效地避免命名冲突、名称前缀的滥用。 |
| 12 | + |
| 13 | +JavaScript默认语法并不支持命名空间,但很容易可以实现此特性。为了避免产生全局污染,你可以为应用或者类库创建一个(通常就一个)全局对象,然后将所有的功能都添加到这个对象上,而不是到处申明大量的全局函数、全局对象以及其他全局变量。 |
| 14 | + |
| 15 | +看如下例子: |
| 16 | + |
| 17 | + // BEFORE: 5 globals |
| 18 | + // Warning: antipattern |
| 19 | + // constructors |
| 20 | + function Parent() {} |
| 21 | + function Child() {} |
| 22 | + // a variable |
| 23 | + var some_var = 1; |
| 24 | + |
| 25 | + // some objects |
| 26 | + var module1 = {}; |
| 27 | + module1.data = {a: 1, b: 2}; |
| 28 | + var module2 = {}; |
| 29 | + |
| 30 | +可以通过创建一个全局对象(通常代表应用名)来重构上述这类代码,比方说, MYAPP,然后将上述例子中的函数和变量都变为该全局对象的属性: |
| 31 | + |
| 32 | + // AFTER: 1 global |
| 33 | + // global object |
| 34 | + var MYAPP = {}; |
| 35 | + |
| 36 | + // constructors |
| 37 | + MYAPP.Parent = function () {}; |
| 38 | + MYAPP.Child = function () {}; |
| 39 | + |
| 40 | + // a variable |
| 41 | + MYAPP.some_var = 1; |
| 42 | + |
| 43 | + // an object container |
| 44 | + MYAPP.modules = {}; |
| 45 | + |
| 46 | + // nested objects |
| 47 | + MYAPP.modules.module1 = {}; |
| 48 | + MYAPP.modules.module1.data = {a: 1, b: 2}; |
| 49 | + MYAPP.modules.module2 = {}; |
| 50 | + |
| 51 | +这里的MYAPP就是命名空间对象,对象名可以随便取,可以是应用名、类库名、域名或者是公司名都可以。开发者经常约定全局变量都采用大写(所有字母都大写),这样可以显得比较突出(不过,要记住,一般大写的变量都用于表示常量)。 |
| 52 | + |
| 53 | +这种模式是一种很好的提供命名空间的方式,避免了自身代码的命名冲突,同时还避免了同一个页面上自身代码和第三方代码(比如:JavaScript类库或者小部件)的冲突。这种模式在大多数情况下非常适用,但也有它的缺点: |
| 54 | + |
| 55 | +* 代码量稍有增加;在每个函数和变量前加上这个命名空间对象的前缀,会增加代码量,增大文件大小 |
| 56 | +* 该全局实例可以被随时修改 |
| 57 | +* 命名的深度嵌套会减慢属性值的查询 |
| 58 | + |
| 59 | +本章后续要介绍的沙箱模式则可以避免这些缺点。 |
| 60 | + |
| 61 | + |
| 62 | +###通用命名空间函数 |
| 63 | + |
| 64 | +随着程序复杂度的提高,代码会分置在不同的文件中以特定顺序来加载,这样一来,就不能保证你的代码一定是第一个申明命名空间或者改变量下的属性的。甚至还会发生属性覆盖的问题。所以,在创建命名空间或者添加属性的时候,最好先检查下是否存在,如下所示: |
| 65 | + |
| 66 | + // unsafe |
| 67 | + var MYAPP = {}; |
| 68 | + // better |
| 69 | + if (typeof MYAPP === "undefined") { |
| 70 | + var MYAPP = {}; |
| 71 | + } |
| 72 | + // or shorter |
| 73 | + var MYAPP = MYAPP || {}; |
| 74 | + |
| 75 | +如上所示,不难看出,如果每次做类似操作都要这样检查一下就会有很多重复性的代码。比方说,要申明**MYAPP.modules.module2**,就要重复三次这样的检查。所以,我们需要一个重用的**namespace()**函数来专门处理这些检查工作,然后用它来创建命名空间,如下所示: |
| 76 | + |
| 77 | + // using a namespace function |
| 78 | + MYAPP.namespace('MYAPP.modules.module2'); |
| 79 | + |
| 80 | + // equivalent to: |
| 81 | + // var MYAPP = { |
| 82 | + // modules: { |
| 83 | + // module2: {} |
| 84 | + // } |
| 85 | + // }; |
| 86 | + |
| 87 | +下面是上述namespace函数的实现案例。这种实现是无损的,意味着如果要创建的命名空间已经存在,则不会再重复创建: |
| 88 | + |
| 89 | + var MYAPP = MYAPP || {}; |
| 90 | + MYAPP.namespace = function (ns_string) { |
| 91 | + var parts = ns_string.split('.'), |
| 92 | + parent = MYAPP, |
| 93 | + i; |
| 94 | + |
| 95 | + // strip redundant leading global |
| 96 | + if (parts[0] === "MYAPP") { |
| 97 | + parts = parts.slice(1); |
| 98 | + } |
| 99 | + |
| 100 | + for (i = 0; i < parts.length; i += 1) { |
| 101 | + // create a property if it doesn't exist |
| 102 | + if (typeof parent[parts[i]] === "undefined") { |
| 103 | + parent[parts[i]] = {}; |
| 104 | + } |
| 105 | + parent = parent[parts[i]]; |
| 106 | + } |
| 107 | + return parent; |
| 108 | + }; |
| 109 | + |
| 110 | +上述实现支持如下使用: |
| 111 | + |
| 112 | + // assign returned value to a local var |
| 113 | + var module2 = MYAPP.namespace('MYAPP.modules.module2'); |
| 114 | + module2 === MYAPP.modules.module2; // true |
| 115 | + |
| 116 | + // skip initial `MYAPP` |
| 117 | + MYAPP.namespace('modules.module51'); |
| 118 | + |
| 119 | + // long namespace |
| 120 | + MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property'); |
| 121 | + |
| 122 | +图5-1 展示了上述代码创建的命名空间对象在Firebug下的可视结果 |
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | +图5-1 MYAPP命名空间在Firebug下的可视结果 |
| 127 | + |
0 commit comments