模块模式

模块模式使用得很广泛,因为它可以为代码提供特定的结构,帮助组织日益增长的代码。不像其它语言,JavaScript没有专门的“包”(package)的语法,但模块模式提供了用于创建独立解耦的代码片段的工具,这些代码可以被当成黑盒,当你正在写的软件需求发生变化时,这些代码可以被添加、替换、移除。

模块模式是我们目前讨论过的好几种模式的组合,即:

  • 命名空间模式
  • 即时函数模式
  • 私有和特权成员模式
  • 依赖声明模式

第一步是初始化一个命名空间。我们使用本章前面部分的namespace()函数,创建一个提供数组相关方法的套件模块:

  1. MYAPP.namespace('MYAPP.utilities.array');

下一步是定义模块。使用一个即时函数来提供私有作用域供私有成员使用。即时函数返回一个对象,也就是带有公有接口的真正的模块,可以供其它代码使用:

  1. MYAPP.utilities.array = (function () {
  2. return {
  3. // todo...
  4. };
  5. }());

下一步,给公有接口添加一些方法:

  1. MYAPP.utilities.array = (function () {
  2. return {
  3. inArray: function (needle, haystack) {
  4. // ...
  5. },
  6. isArray: function (a) {
  7. // ...
  8. }
  9. };
  10. }());

如果需要的话,你可以在即时函数提供的闭包中声明私有属性和私有方法。同样,依赖声明放置在函数顶部,在变量声明的下方可以选择性地放置辅助初始化模块的一次性代码。函数最终返回的是一个包含模块公共API的对象:

  1. MYAPP.namespace('MYAPP.utilities.array');
  2. MYAPP.utilities.array = (function () {
  3. // 依赖声明
  4. var uobj = MYAPP.utilities.object,
  5. ulang = MYAPP.utilities.lang,
  6. // 私有属性
  7. array_string = "[object Array]",
  8. ops = Object.prototype.toString;
  9. // 私有方法
  10. // ……
  11. // 结束变量声明
  12. // 选择性放置一次性初始化的代码
  13. // ……
  14. // 公有API
  15. return {
  16. inArray: function (needle, haystack) {
  17. for (var i = 0, max = haystack.length; i < max; i += 1) {
  18. if (haystack[i] === needle) {
  19. return true;
  20. }
  21. }
  22. },
  23. isArray: function (a) {
  24. return ops.call(a) === array_string;
  25. }
  26. // ……更多的方法和属性
  27. };
  28. }());

模块模式被广泛使用,是一种值得强烈推荐的模式,它可以帮助我们组织代码,尤其是代码量在不断增长的时候。

暴露模块模式

我们在本章中讨论私有成员模式时已经讨论过暴露模式。模块模式也可以用类似的方法来组织,将所有的方法保持私有,只在最后暴露需要使用的方法来初始化API。

上面的例子可以变成这样:

  1. MYAPP.utilities.array = (function () {
  2. // 私有属性
  3. var array_string = "[object Array]",
  4. ops = Object.prototype.toString,
  5. // 私有方法
  6. inArray = function (haystack, needle) {
  7. for (var i = 0, max = haystack.length; i < max; i += 1) {
  8. if (haystack[i] === needle) {
  9. return i;
  10. }
  11. }
  12. return 1;
  13. },
  14. isArray = function (a) {
  15. return ops.call(a) === array_string;
  16. };
  17. // 结束变量定义
  18. // 暴露公有API
  19. return {
  20. isArray: isArray,
  21. indexOf: inArray
  22. };
  23. }());

创建构造函数的模块

前面的例子创建了一个对象MYAPP.utilities.array,但有时候使用构造函数来创建对象会更方便。你也可以同样使用模块模式来做。唯一的区别是包裹模块的即时函数会在最后返回一个函数,而不是一个对象。

看下面的模块模式的例子,创建了一个构造函数MYAPP.utilities.Array

  1. MYAPP.namespace('MYAPP.utilities.Array');
  2. MYAPP.utilities.Array = (function () {
  3. // 依赖声明
  4. var uobj = MYAPP.utilities.object,
  5. ulang = MYAPP.utilities.lang,
  6. // 私有属性和方法……
  7. Constr;
  8. // 结束变量定义
  9. // 选择性放置一次性初始化代码
  10. // ……
  11. // 公有API——构造函数
  12. Constr = function (o) {
  13. this.elements = this.toArray(o);
  14. };
  15. // 公有API——原型
  16. Constr.prototype = {
  17. constructor: MYAPP.utilities.Array,
  18. version: "2.0",
  19. toArray: function (obj) {
  20. for (var i = 0, a = [], len = obj.length; i < len; i += 1) {
  21. a[i] = obj[i];
  22. }
  23. return a;
  24. }
  25. };
  26. // 返回构造函数
  27. return Constr;
  28. }());

像这样使用这个新的构造函数:

  1. var arr = new MYAPP.utilities.Array(obj);

在模块中引入全局上下文

作为这种模式的一个常见的变种,你可以给包裹模块的即时函数传递参数。你可以传递任何值,但通常情况下会传递全局变量甚至是全局对象本身。引入全局上下文可以加快函数内部的全局变量的解析,因为引入之后会作为函数的本地变量:

  1. MYAPP.utilities.module = (function (app, global) {
  2. // 全局对象和全局命名空间都作为本地变量存在
  3. }(MYAPP, this));