`
tianya84
  • 浏览: 25773 次
社区版块
存档分类
最新评论

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 aclosure, 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 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!

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.

 

from: http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth

分享到:
评论

相关推荐

    Design pattern In JavaScrip-design-pattern-in-javascript.zip

    "Design pattern In JavaScript"这个主题涵盖了一系列广泛使用的模式,这些模式可以分为三大类:创建型、结构型和行为型。 1. **创建型模式**: - **工厂模式**:它提供了一种创建对象的接口,但允许子类决定实例...

    PRG-Pattern:发布 - 重定向 - 获取模式

    PRG-Pattern 发布 - 重定向 - 获取模式 有关更多信息,请尝试维基百科 “您要再次将 POST 数据重新发送到网站吗?” 用户在尝试刷新或返回页面时收到的错误消息真的让他们感到害怕。 可以理解,因为浏览器经常说...

    movies-javascript-bolt:使用neo4j-javascript-driver在webpack-in-browser应用中的Neo4j电影示例

    应用程序类型:JavaScript-Web应用程序 Neo4j数据库连接器:用于Cypher 数据库:具有多个数据库的Neo4j-Server(4.x) 前端:jquery,bootstrap, 使用或快速配置数据库。 快速开始 设置 $ npm install 在本地...

    Javascript.Object.Oriented.Programming.pdf

    The third module takes you through all the in-depth and exciting futures hidden behind the facade. You should read through this course if you want to be able to take your JavaScript skills to a new ...

    hbq-module-cli-boilerplate:hbq-module-cli-boilerplate

    `hbq-module-cli-boilerplate` 是一个专为开发命令行接口(CLI)工具而设计的模板项目。这个模板提供了快速高效地构建CLI工具的基础框架,尤其适合那些想要使用TypeScript进行开发的开发者。让我们深入了解一下这个...

    shellcode帮助工具,直接把exe转shellcode

    -search <pattern> get the start offset by the pattern: e.g. PK\x03\x04 -soff <offset> fix the match offset after searching (Default: 0) -off <offset> convert the input file from the offset (Default...

    前端开源库-replace-in-file-webpack-plugin

    **replace-in-file-webpack-plugin** 是一个前端开源的Webpack插件,专用于在构建过程中查找并替换项目中的文件内容。在现代Web开发中,Webpack作为模块打包工具,扮演着至关重要的角色,它允许开发者将JavaScript、...

    django-framework-module:django-framework-module

    Gitpod提醒要在Gitpod中运行前端(仅HTML,CSS,Javascript)应用程序,请在终端中输入: python3 -m http.server 应该出现一个蓝色按钮,以单击:公开, 应该出现另一个蓝色按钮,以单击:打开浏览器。 要运行后端...

    JS-module-06:JS-自动检查模块-06

    在这个"JS-module-06: JS-自动检查模块-06"的主题中,我们将深入探讨JavaScript模块系统的核心概念,特别是自动检查模块的实现和应用。 首先,我们需要理解JavaScript的模块化是如何工作的。在ES6(ECMAScript 2015...

    commitlint-github-action:Lints Pull Request使用commitlint提交

    Commitlint Github动作 Lints Pull Request使用commitlint提交 ... fetch-depth : 0 - uses : wagoid/commitlint-github-action@v3 或者,您可以在其他事件类型on: [push]运行,例如on: [push] 。 在这

    echo-plug-in:app-echo.com插件

    echo-plug-in是为了解决JavaScript在特定场景下的扩展性和定制性问题而诞生的。它可能包含了如事件监听、数据处理、网络请求、用户交互等多种功能,旨在提高开发效率并简化代码结构。 在"echo-plug-in-master"这个...

    CAPG-MODULE-1-P-Ganesh-Kumar:Lab1-4的文件

    "CAPG-MODULE-1-P-Ganesh-Kumar:Lab1-4的文件"标题和描述指向的可能是一个教学或实践课程中的模块,由Ganesh Kumar指导,专注于JavaScript的基础或进阶学习。这个模块可能包含了一系列的实验或练习,旨在帮助学习者...

    wrlc:观看并提供您的 browserify 包

    -r, --require=MODULE : -e, --entry=FILE : -i, --ignore=文件 : -u, --exclude=FILE : -x, --external : -t, --transform=MODULE : -c, --command=COMMAND : --d, --debug : --h, --help :

    javascript-design-pattern-course-project:Udacity的Javascript设计模式课程项目

    javascript-design-pattern-course-project 来自Udacity的Javascript设计模式课程项目: ://www.udacity.com/course/viewer#! c-ud989/l-3417188540/m-3433869050

    boilerplate-webpack-react-es6-cssModule:Webpack-React-ES6-CssModule项目的样板

    Hot Module Replacement 热加载 ESLint 检测 less、autoprefixer 业务组件使用 cssModule,通用组件使用 BEM 命名法 小于 8k 图片转为 base64 svg 图标 文件压缩、添加 MD5 ES6+, Fetch 使用 Redux ...

    kraken-devtools-browserify:kraken-devtools 的 Browserify 插件

    kraken-devtools-browserify ... 到 middleware.devtools.module[name=kraken-devtools].arguments 下的 kraken 开发配置(config/development.json)。 应该是这样的: "middleware": { ... "devtools": { ...

    pattern-presenter:在页面中显示样式以进行样式设置

    开始的步骤使用yo atlas安装 Atlas通过 bower 获取模式库bower install git@github.com:pattern-library/pattern-library.git --save 添加模式导入器作为节点模块: npm install git+ssh://git@github....

    module-pattern-javascript

    模块模式 Javascript介绍这个项目只是为了测试模块模式javascript要求安装$ git clone git@github.com:kiki-le-singe/module-pattern-javascript.git $ bower install包含的 JavaScript 库来源

    pattern-replace-loader:web用于Webpack的图案替换加载器

    安装: $ npm install --save-dev pattern-replace-loader 或使用纱线$ yarn add pattern-replace-loader --dev用法: Plain :它使用在文件内容中执行替换。 正则表达式:它将查找您在options.search指定的所有内容...

Global site tag (gtag.js) - Google Analytics