首页 技术 正文
技术 2022年11月18日
0 收藏 912 点赞 2,929 浏览 10322 个字

The module pattern is a common JavaScript coding pattern. It’s generally well understood, but there are a number of advanced uses that have not gotten a lot of attention. In this article, I’ll review the basics and cover some truly remarkable advanced topics, including one which I think is original.

The Basics

We’ll start out with a simple overview of the module pattern, which has been well-known since Eric Miraglia (of YUI) first blogged about it three years ago. If you’re already familiar with the module pattern, feel free to skip ahead to “Advanced Patterns”.

Anonymous Closures

This is the fundamental construct that makes it all possible, and really is the single best feature of JavaScript. We’ll simply create an anonymous function, and execute it immediately. All of the code that runs inside the function lives in a closure, which provides privacy and state throughout the lifetime of our application.

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

Notice the () around the anonymous function. This is required by the language, since statements that begin with the token function are always considered to be function declarations. Including () creates a function expression instead.

Global Import

JavaScript has a feature known as implied globals. Whenever a name is used, the interpreter walks the scope chain backwards looking for a var statement for that name. If none is found, that variable is assumed to be global. If it’s used in an assignment, the global is created if it doesn’t already exist. This means that using or creating global variables in an anonymous closure is easy. Unfortunately, this leads to hard-to-manage code, as it’s not obvious (to humans) which variables are global in a given file.

Luckily, our anonymous function provides an easy alternative. By passing globals as parameters to our anonymous function, we import them into our code, which is both clearer and faster than implied globals. Here’s an example:

(function ($, YAHOO) {
// now have access to globals jQuery (as $) and YAHOO in this code
}(jQuery, YAHOO));

Module Export

Sometimes you don’t just want to use globals, but you want to declare them. We can easily do this by exporting them, using the anonymous function’s return value. Doing so will complete the basic module pattern, so here’s a complete example:

var MODULE = (function () {
var my = {},
privateVariable = 1;function privateMethod() {
// ...
}my.moduleProperty = 1;
my.moduleMethod = function () {
// ...
};return my;
}());

Notice that we’ve declared a global module named MODULE, with two public properties: a method named MODULE.moduleMethod and a variable named MODULE.moduleProperty. In addition, it maintains private internal stateusing the closure of the anonymous function. Also, we can easily import needed globals, using the pattern we learned above.

Advanced Patterns

While the above is enough for many uses, we can take this pattern farther and create some very powerful, extensible constructs. Lets work through them one-by-one, continuing with our module named MODULE.

Augmentation

One limitation of the module pattern so far is that the entire module must be in one file. Anyone who has worked in a large code-base understands the value of splitting among multiple files. Luckily, we have a nice solution to augment modules. First, we import the module, then we add properties, then we export it. Here’s an example, augmenting our MODULE from above:

var MODULE = (function (my) {
my.anotherMethod = function () {
// added method...
};return my;
}(MODULE));

We use the var keyword again for consistency, even though it’s not necessary. After this code has run, our module will have gained a new public method named MODULE.anotherMethod. This augmentation file will also maintain its own private internal state and imports.

Loose Augmentation

While our example above requires our initial module creation to be first, and the augmentation to happen second, that isn’t always necessary. One of the best things a JavaScript application can do for performance is to load scripts asynchronously. We can create flexible multi-part modules that can load themselves in any order with loose augmentation. Each file should have the following structure:

var MODULE = (function (my) {
// add capabilities...return my;
}(MODULE || {}));

In this pattern, the var statement is always necessary. Note that the import will create the module if it does not already exist. This means you can use a tool like LABjs and load all of your module files in parallel, without needing to block.

Tight Augmentation

While loose augmentation is great, it does place some limitations on your module. Most importantly, you cannot override module properties safely. You also cannot use module properties from other files during initialization (but you can at run-time after intialization). Tight augmentation implies a set loading order, but allows overrides. Here is a simple example (augmenting our original 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));

Here we’ve overridden MODULE.moduleMethod, but maintain a reference to the original method, if needed.

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));

This pattern is perhaps the least flexible option. It does allow some neat compositions, but that comes at the expense of flexibility. As I’ve written it, properties which are objects or functions will not be duplicated, they will exist as one object with two references. Changing one will change the other. This could be fixed for objects with a recursive cloning process, but probably cannot be fixed for functions, except perhaps with eval. Nevertheless, I’ve included it for completeness.

Cross-File Private State

One severe limitation of splitting a module across multiple files is that each file maintains its own private state, and does not get access to the private state of the other files. This can be fixed. Here is an example of a loosely augmented module that will maintain private state across all augmentations:

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 _unsealreturn my;
}(MODULE || {}));

Any file can set properties on their local variable _private, and it will be immediately available to the others. Once this module has loaded completely, the application should call MODULE._seal(), which will prevent external access to the internal _private. If this module were to be augmented again, further in the application’s lifetime, one of the internal methods, in any file, can call _unseal() before loading the new file, and call _seal() again after it has been executed. This pattern occurred to me today while I was at work, I have not seen this elsewhere. I think this is a very useful pattern, and would have been worth writing about all on its own.

Sub-modules

Our final advanced pattern is actually the simplest. There are many good cases for creating sub-modules. It is just like creating regular modules:

MODULE.sub = (function () {
var my = {};
// ...return my;
}());

While this may have been obvious, I thought it worth including. Sub-modules have all the advanced capabilities of normal modules, including augmentation and private state.

Conclusions

Most of the advanced patterns can be combined with each other to create more useful patterns. If I had to advocate a route to take in designing a complex application, I’d combine loose augmentationprivate state, and sub-modules.

I haven’t touched on performance here at all, but I’d like to put in one quick note: The module pattern is good for performance. It minifies really well, which makes downloading the code faster. Using loose augmentation allows easy non-blocking parallel downloads, which also speeds up download speeds. Initialization time is probably a bit slower than other methods, but worth the trade-off. Run-time performance should suffer no penalties so long as globals are imported correctly, and will probably gain speed in sub-modules by shortening the reference chain with local variables.

To close, here’s an example of a sub-module that loads itself dynamically to its parent (creating it if it does not exist). I’ve left out private state for brevity, but including it would be simple. This code pattern allows an entire complex heirarchical code-base to be loaded completely in parallel with itself, sub-modules and all.

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!

中文解释:

模块模式是JavaScript一种常用的编码模式。这是一般的理解,但也有一些高级应用没有得到很多关注。在本文中,我将回顾基础知识,浏览一些不错的高级技巧,甚至我认为是原生基础的。 

基础知识

首先我们开始简单概述模型模式。三年前Eric Miraglia(YUI)的博文使模型模式众所周知。如果你已经很熟悉模型模式,可以直接阅读“高级模式”。

匿名闭包

这是一切成为可能的基础,也是JavaScript最好的特性。我们将简单的创建匿名函数,并立即执行。所有函数内部代码都在闭包(closure)内。它提供了整个应用生命周期的私有和状态。

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

注意匿名函数周围的()。这是语言的要求。关键字function一般认为是函数声明,包括()就是函数表达式。

 

引入全局

JavaScript有个特性,称为隐性全局。使用变量名称时,解释器会从作用域向后寻找变量声明。如果没找到,变量会被假定入全局(以后可以全局调用)。如果会被分配使用,在还不存在时全局创建它。这意味着在匿名函数里使用全局变量很简单。不幸的是,这会导致代码难以管理,文件中不容易区分(对人而言)哪个变量是全局的。

幸好,匿名函数还有一个不错的选择。全局变量作为参数传递给匿名函数。将它们引入我们的代码中,既更清晰,又比使用隐性全局更快。下面是一个例子:

(function ($, YAHOO) {
// 当前域有权限访问全局jQuery($)和YAHOO
}(jQuery, YAHOO));

 LABjs一样同时加载所有模块文件而不被阻塞。

  

紧耦合扩充

虽然松耦合很不错,但模块上也有些限制。最重要的,你不能安全的覆写模块属性(因为没有加载顺序)。初始化时也无法使用其他文件定义的模块属性(但你可以在初始化后运行)。紧耦合扩充意味着一组加载顺序,但是允许覆写。下面是一个例子(扩充最初定义的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,但依旧保持着私有内部状态

 

克隆和继承

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));

这种方式也许最不灵活。他可以实现巧妙的组合,但是牺牲了灵活性。正如我写的,对象的属性或方法不是拷贝,而是一个对象的两个引用。修改一个会影响其他。这可能可以保持递归克隆对象的属性固定,但无法固定方法,除了带eval的方法。不过,我已经完整的包含了模块。(其实就是做了一次浅拷贝)。

 

跨文件私有状态

一个模块分割成几个文件有一个严重缺陷。每个文件都有自身的私有状态,且无权访问别的文件的私有状态。这可以修复的。下面是一个松耦合扩充的例子,不同扩充文件之间保持了私有状态:

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;
},//模块加载后,调用以移除对_private的访问权限
_unseal = my._unseal = my._unseal || function () {
my._private = _private;
my._seal = _seal;
my._unseal = _unseal;
};//模块加载前,开启对_private的访问,以实现扩充部分对私有内容的操作// permanent access to _private, _seal, and _unsealreturn my;
}(MODULE || {}));

任何文件都可以在本地的变量_private中设置属性,他会对别的扩充立即生效(即初始化时所有扩充的私有状态都保存在_private变量,并被my._private输出)。模块完全加载了,应用调用MODULE._seal()方法阻止对私有属性的读取(干掉my._private输出)。如果此后模块又需要扩充,带有一个私有方法。加载扩充文件前调用MODULE._unseal()方法(恢复my._private,外部恢复操作权限)。加载后调用再seal()。

子模块

最后的高级模式实际上最简单。有很多好方法创建子模块。和创建父模块是一样的:

MODULE.sub = (function () {
var my = {};
// 就是多一级命名空间return my;
}());

虽然很简单,但我还是提一下。子模块有所有正常模块的功能,包括扩充和私有状态。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,031
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,520
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,368
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,148
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,781
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,860