模块模式
模块模式使用得很广泛,因为它可以为代码提供特定的结构,帮助组织日益增长的代码。不像其它语言,JavaScript没有专门的“包”(package)的语法,但模块模式提供了用于创建独立解耦的代码片段的工具,这些代码可以被当成黑盒,当你正在写的软件需求发生变化时,这些代码可以被添加、替换、移除。
模块模式是我们目前讨论过的好几种模式的组合,即:
- 命名空间模式
- 即时函数模式
- 私有和特权成员模式
- 依赖声明模式
第一步是初始化一个命名空间。我们使用本章前面部分的namespace()
函数,创建一个提供数组相关方法的套件模块:
MYAPP.namespace('MYAPP.utilities.array');
下一步是定义模块。使用一个即时函数来提供私有作用域供私有成员使用。即时函数返回一个对象,也就是带有公有接口的真正的模块,可以供其它代码使用:
MYAPP.utilities.array = (function () {
return {
// todo...
};
}());
下一步,给公有接口添加一些方法:
MYAPP.utilities.array = (function () {
return {
inArray: function (needle, haystack) {
// ...
},
isArray: function (a) {
// ...
}
};
}());
如果需要的话,你可以在即时函数提供的闭包中声明私有属性和私有方法。同样,依赖声明放置在函数顶部,在变量声明的下方可以选择性地放置辅助初始化模块的一次性代码。函数最终返回的是一个包含模块公共API的对象:
MYAPP.namespace('MYAPP.utilities.array');
MYAPP.utilities.array = (function () {
// 依赖声明
var uobj = MYAPP.utilities.object,
ulang = MYAPP.utilities.lang,
// 私有属性
array_string = "[object Array]",
ops = Object.prototype.toString;
// 私有方法
// ……
// 结束变量声明
// 选择性放置一次性初始化的代码
// ……
// 公有API
return {
inArray: function (needle, haystack) {
for (var i = 0, max = haystack.length; i < max; i += 1) {
if (haystack[i] === needle) {
return true;
}
}
},
isArray: function (a) {
return ops.call(a) === array_string;
}
// ……更多的方法和属性
};
}());
模块模式被广泛使用,是一种值得强烈推荐的模式,它可以帮助我们组织代码,尤其是代码量在不断增长的时候。
暴露模块模式
我们在本章中讨论私有成员模式时已经讨论过暴露模式。模块模式也可以用类似的方法来组织,将所有的方法保持私有,只在最后暴露需要使用的方法来初始化API。
上面的例子可以变成这样:
MYAPP.utilities.array = (function () {
// 私有属性
var array_string = "[object Array]",
ops = Object.prototype.toString,
// 私有方法
inArray = function (haystack, needle) {
for (var i = 0, max = haystack.length; i < max; i += 1) {
if (haystack[i] === needle) {
return i;
}
}
return −1;
},
isArray = function (a) {
return ops.call(a) === array_string;
};
// 结束变量定义
// 暴露公有API
return {
isArray: isArray,
indexOf: inArray
};
}());
创建构造函数的模块
前面的例子创建了一个对象MYAPP.utilities.array
,但有时候使用构造函数来创建对象会更方便。你也可以同样使用模块模式来做。唯一的区别是包裹模块的即时函数会在最后返回一个函数,而不是一个对象。
看下面的模块模式的例子,创建了一个构造函数MYAPP.utilities.Array
:
MYAPP.namespace('MYAPP.utilities.Array');
MYAPP.utilities.Array = (function () {
// 依赖声明
var uobj = MYAPP.utilities.object,
ulang = MYAPP.utilities.lang,
// 私有属性和方法……
Constr;
// 结束变量定义
// 选择性放置一次性初始化代码
// ……
// 公有API——构造函数
Constr = function (o) {
this.elements = this.toArray(o);
};
// 公有API——原型
Constr.prototype = {
constructor: MYAPP.utilities.Array,
version: "2.0",
toArray: function (obj) {
for (var i = 0, a = [], len = obj.length; i < len; i += 1) {
a[i] = obj[i];
}
return a;
}
};
// 返回构造函数
return Constr;
}());
像这样使用这个新的构造函数:
var arr = new MYAPP.utilities.Array(obj);
在模块中引入全局上下文
作为这种模式的一个常见的变种,你可以给包裹模块的即时函数传递参数。你可以传递任何值,但通常情况下会传递全局变量甚至是全局对象本身。引入全局上下文可以加快函数内部的全局变量的解析,因为引入之后会作为函数的本地变量:
MYAPP.utilities.module = (function (app, global) {
// 全局对象和全局命名空间都作为本地变量存在
}(MYAPP, this));