2010-03-12

JavaScript Module Pattern: In-Depth

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) firstblogged 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 tokenfunction 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 namedMODULE.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 _unseal return 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 augmentation, private 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!

This post was featured on Ajaxian.com, and there is a little bit more discussion going on there as well, which is worth reading in addition to the comments below.

filed under javascript and module pattern

JavaScript Module Pattern: In-Depth的更多相关文章

  1. JavaScript module pattern精髓

    JavaScript module pattern精髓 avaScript module pattern是一种常见的javascript编码模式.这种模式本身很好理解,但是有很多高级用法还没有得到大家 ...

  2. 玩转JavaScript module pattern精髓

    JavaScript module pattern是一种常见的javascript编码模式.这种模式本身很好理解,但是有很多高级用法还没有得到大家的注意.本文,我们将回顾这种设计模式,并且介绍一些高级 ...

  3. Javascript Module pattern template. Shows a class with a constructor and public/private methods/properties. Also shows compatibility with CommonJS(eg Node.JS) and AMD (eg requireJS) as well as in a br

    /** * Created with JetBrains PhpStorm. * User: scotty * Date: 28/08/2013 * Time: 19:39 */ ;(function ...

  4. Learning JavaScript Design Patterns The Module Pattern

    The Module Pattern Modules Modules are an integral piece of any robust application's architecture an ...

  5. JavaScript Patterns 5.4 Module Pattern

    MYAPP.namespace('MYAPP.utilities.array'); MYAPP.utilities.array = (function () { // dependencies var ...

  6. Understanding the Module Pattern in JavaScript

    Understanding the Module Pattern in JavaScript Of all the design patterns you are likely to encounte ...

  7. [Javascript ] Array methods in depth - sort

    Sort can automatically arrange items in an array. In this lesson we look at the basics including how ...

  8. javascript module system all in one

    javascript module system all in one AMD & CMD https://github.com/amdjs/amdjs-api/wiki/AMD http:/ ...

  9. JavaScript基础对象创建模式之模块模式(Module Pattern)(025)

    模块模式可以提供软件架构,为不断增长的代码提供组织形式.JavaScript没有提供package的语言表示,但我们可以通过模块模式来分解并组织 代码块,这些黑盒的代码块内的功能可以根据不断变化的软件 ...

随机推荐

  1. 【OS】分页和分段

    在网上找到了一个比较形象的比喻   打个比方,比如说你去听课,带了一个纸质笔记本做笔记.笔记本有100张纸,课程有语文.数学.英语三门,对于这个笔记本的使用,为了便于以后复习方便,你可以有两种选择. ...

  2. 严重:Error listenerStart

    转自: http://1985wanggang.blog.163.com/blog/static/7763833200942611050436/ 近日浏览论坛,发现好多人提问,都说在运行web程序时, ...

  3. [WPF]ComboBox.Items为空时,点击不显示下拉列表

    ComboBox.Items为空时,点击后会显示空下拉列表: ComboBox点击显示下拉列表,大概原理为: ComboBox存在ToggleButton控件,默认ToggleButton.IsChe ...

  4. wmware 怎么 跟主机相互通信

    VMnet1和VMware8其实就是软件模拟出来的两块网卡提供DHCP服务,两块网卡对应VMware的两种不同的模式VMWare提供了三种工作模式,它们是bridged(桥接模式).NAT(网络地址转 ...

  5. ajax基础1

    AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况 ...

  6. Python’s SQLAlchemy vs Other ORMs[转发 7] 比较结论

    Comparison Between Python ORMs For each Python ORM presented in this article, we are going to list t ...

  7. 【NodeJS线程】Boss和他的职员们

    >>>[说明]还是一如既往的,这篇文章是从我的个人博客里挪过来的.原文参见:http://www.jscon.co/coding/frontend/nodejs_fork_child ...

  8. WordPress登陆页和后台面空白解决方法

    真没想到我居然也会碰到这么蛋疼的事情,有一天我登陆博客,输入账号密码之后登陆没有反应,之后我就试着用首页前台登陆(因为这个模板前台带登陆功能),之后成功登陆进入后台更新文章.我想算了.这小毛病就丢那吧 ...

  9. 初识ReactJs(一)

    React的开发背景 ReactJS官网地址:http://facebook.github.io/react/ Github地址:https://github.com/facebook/react J ...

  10. F2工作流引擎这工作流引擎体系架构(二)

    F2工作流体系架构概览图 为了能更好的了解F2工作流引擎的架构体系,花了些时间画了整个架构的体系图.F2工作流引擎遵循参考WFCM规范,目标是实现轻量级的工作流引擎,支持多种数据库及快速应用到任何基于 ...