您的位置:首页 > Web前端 > JavaScript

转载翻译文章:JavaScript Module Pattern: In-Dept

2015-08-21 00:19 489 查看
# JavaScript Module Pattern: In-Depth

# 转载翻译文章:JavaScript Module Pattern: In-Depth
*原文*:http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html

模块模式是一种常见的js开发编码模式。通常来说,它很好理解,但还有一些高级应用并没有得到太多关注。这篇文章,我将回顾基础知识,并介绍一些真正了不起的高级主题,其中包括一个我认为极为原始的一个。

## The Basics
我们将开始于一个模块模式的简单概述。模块模式,从三年前YUI的Eric Miraglia发表于他的博客中开始,便被我们所熟知。如果你已经对模块模式相当熟悉,请跳到“高级模式”(Advanced Patterns)。

### Anonymous Closures(匿名闭包)
这就是使一切成为可能的基本结构,它确实是javascript独一无二的一个最大特点。我们简单的创建一个匿名函数,然后马上执行它。函数中的全部代码,存在于一个闭包里,这个闭包为整个应用程序运行生命周期提供了私有的环境和状态。

```
(function () {
// ... all vars and functions are in this scope only
// still maintains access to all globals
}());
```

应该注意到,匿名函数是被一个括号“()”包裹住的。这是语言上的要求,因为开始于function关键字的语句块总是被认为是函数的声明,使用“()”使得它变成了一个函数表达式。

### Global Import
JavaScript有一个被称为“implied globals”的特征。每当一个变量名称被使用,解析器就会回溯整个作用域链,去为这个变量名称寻找一个“var”声明语句。如果没有找到,这个变量就会被认为是全局变量。如果这个变量已被分配使用的,并且是全局的,就会创建这全局变量。这意味着在匿名闭包中使用或创建全局变量是很容易的。很不幸,这将导致代码难以管理,因为对编码者来说,一个给定的文件中全局变量并不是特别的明显。

幸运的是,我们的匿名函数提供了一个简单的选择。通过把全局变量作为参数传递金匿名函数里,我们就把全局变量import进了我们的代码,这会比“implied globals”来的要更快和更清晰。下面是一个例子:
```
(function ($, YAHOO) {
// now have access to globals jQuery (as $) and YAHOO in this code
}(jQuery, YAHOO));
```

### Module Export
有时候你并不想马上使用,但你想去声明他们。我们可以很容易的使用匿名函数的return返回值输出他们。这样子做将会完成基本的模块模式。下面是一个完整的例子
```
var MODULE = (function () {
var my = {},
privateVariable = 1;

function privateMethod() {
// ...
}

my.moduleProperty = 1;
my.moduleMethod = function () {
// ...
};

return my;
}());
```
应注意到,我们声明了一个叫做“MODULE”的全局模块,包含两个公共属性:一个名为MODULE.moduleMethod的方法,一个名为 MODULE.moduleProperty的变量。此外,它使用闭包中的匿名函数保持私有的内部状态。再者,我们也能使用我们上面学习到的模式轻易地导入所需要的全局模块(变量)。

## Advanced Patterns
虽然上述描述已经足够我们的许多用途,我们还可以让这个模式走得更远,去创造一些十分强大的、可扩展的结构。让我们继续以模块MODULE为名,一个接一个的运用它们。

### Augmentation

迄今为止模块模式的一个限制是整个模块必须是在一个文件中。所有在这个巨大的代码库上工作的人必须理解存在于这个复杂文件中的分裂。幸运的是,我们有一个很好的扩充模块的解决方案。首先,我们导入模块,然后添加属性,最后导出。下面是例子,扩充我们上面的MODULE模块:
```
var MODULE = (function (my) {
my.anotherMethod = function () {
// added method...
};

return my;
}(MODULE));
```

我们再次使用var关键字的一致性,即使它不是必需的。运行这段代码之后,我们的模块将获得一个名为MODULE.anotherMethod一个新的公共方法。这扩展文件也将保持它自己的私有内部状态和导入模块。

### Loose Augmentation(宽松的扩展)

我们上面的例子要求我们首先创建初始化模块,然后才扩充它,但是我们其实不是必须的。JavaScript应用对于呈现最好的一个东西就是脚本的异步加载。运用这个宽松扩展,我们可以创建灵活的,可以已任意顺序加载自己的多部分模块。每个文件都应该含有下面这个结构:
```
var MODULE = (function (my) {
// add capabilities...

return my;
}(MODULE || {}));
```

在这个模式中,var关键字总是必须的。应注意到,引入的模块将会被自动创建,如果它不存在的话。这意味着,你可以使用一些工具,诸如LABjs,无阻塞地并行加载你的模块文件。

### Tight Augmentation(严格的扩展)

虽然宽松的扩展非常的棒,不过它对于你的模块仍然有着一些限制。最重要的是你不能安全的重写模块属性。你也不能在初始化期间使用来源于别的文件的模块属性(不过在初始化完成后的运行期则是可以的)。严格扩展意味着一组顺序的加载,不过它允许重写。下面是一个简单例子(扩展哦们的原始MODULE模块):

var MODULE = (function (my) {
var old_moduleMethod = my.moduleMethod;

my.moduleMethod = function () {
// method override, has access to old through old_moduleMethod...
};

return my;
}(MODULE));

这里我们重写了MODULE.moduleMethod方法,不过维持着一个指向原始方法的关联,如果需要的话。

### Cloning and Inheritance(克隆和继承)
```
var MODULE_TWO = (function (old) {
var my = {},
key;

for (key in old) {
if (old.hasOwnProperty(key)) {
my[key] = old[key];
}
}

var super_moduleMethod = old.moduleMethod;
my.moduleMethod = function () {
// override method on the clone, access to super through super_moduleMethod
};

return my;
}(MODULE));
```

这个模式可能是最不灵活的选择。它允许一些整齐的组合,但是那是以牺牲灵活性为代价的。就像我之前所写的,对象或函数不会被真正复制的特性,它们会存在一个对象的两个关联。改变一个会跟着改变另一个。对于对象,这个问题可以通过递归的克隆过程得到修复,但是对于function来说则无法修复,或许使用eval()可以。
虽然这样,但是为了完整性,我还是包含了它。

### Cross-File Private State

对于在多文件中分割开的模块来说,一个严重的限制是每个文件维持着自己的一个私有的状态,并却无法访问其他文件的私有环境。这是可以解决的。以下是一个宽松扩展模块的例子,它会持有跨越所有扩展的私有环境:
```
var MODULE = (function (my) {
var _private = my._private = my._private || {},
_seal = my._seal = my._seal || function () {
delete my._private;
delete my._seal;
delete my._unseal;
},
_unseal = my._unseal = my._unseal || function () {
my._private = _private;
my._seal = _seal;
my._unseal = _unseal;
};

// permanent access to _private, _seal, and _unseal

return my;
}(MODULE || {}));
```

任何文件可以对自己的局部变量_private设置属性,它会马上生效。一旦这个模块加载完成,应用程序应该调用MODULE._seal()方法,它会阻止外部访问内部的_private。如果这个模块被再次添加,在它未来的应用程序生命周期,任何文件的某个内部方法,可以在加载新文件之前调用_unseal()方法,然后在它执行后再次调用_seal()方法。这个模式是我今天工作的时候出现在我的脑海的,我从来没有在别处看见过这种用法。我想这是一个超级有用的模式,它事实上已经被证明非常值得一写。

### Sub-modules

我们最后一个高级模式实际上是最简单的一个。有许多创建sub-modules的好例子。正像创建普通模式一样:
```
MODULE.sub = (function () {
var my = {};
// ...

return my;
}());
```

虽然它可能比较显浅,但我还是觉得它值得被列入(我们的模式)。子模块含有所有普通模块的先进功能,包括扩展和私有状态。

## Conclusions(结论)

所有先进模块可以相互结合以创建更多有用的模式。如果我必须提出一个设计复杂应用的路线,我会综合loose augmentation(宽松扩展), private state(私有状态), and sub-modules(子模块)这三个。

这里我没有深入展开,但我会给出一些快速结论:这个模式的表现非常的好。它压缩的很好,让代码的加载更快。使用宽松的扩展使得非阻塞的一部加载变得容易,这也加速了下载速度。初始化的时间可能要比其他模式要慢一些,但这值得我们去权衡。只要全局变量已经正确导入,运行时的表现应该不会有什么问题,而且,在子模块中由于通过局部变量缩短了关联作用链,速度将更加的快。

作为结束,这里是一个子模块动态加载自身其父的例子(当不存在时创建)。我这里抛弃了私有环境的简洁,不过要包含它也是一件单间的事。此代码模式允许整个复杂结构的代码库自身,包括子模块和所有其他东西一起并行加载。
```
var UTIL = (function (parent, $) {
var my = parent.ajax = parent.ajax || {};

my.get = function (url, params, callback) {
// ok, so I'm cheating a bit :)
return $.getJSON(url, params, callback);
};

// etc...

return parent;
}(UTIL || {}, jQuery));
```
I hope this has been useful, and please leave a comment to share your thoughts. Now, go forth and write better, more modular JavaScript!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: