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

我的问题

在我的项目中经常会在一个jsp中import包含下面这样的JavaScript代码的文件:

var myBrand = {
name:"xxx"
}; var isBrand = function(brand) {
return brand === "xxx"
}

在和我们公司一位非常senior的同事陈显军结对编程的时候,得知:定义在所有函数最外边,使用或不使用var关键字定义的变量都是全局变量。而且最终合并成一个js文件,也就是说这种方式定义方法和变量是非常危险而且非常容易污染一些其他框架中的全局变量。这也就是为什么我会翻译这篇JavaScript module pattern。一些常用的框架像JQuery和Underscore都是采用这种模块化的方式来实现的。

基础用法

我们会先从介绍module模式,如果你对module模式比较熟悉,可以直接跳到高级用法处。

1.匿名闭包

这是一种基本的javascipt构造方法,也是javascipt中最好的特性,我们申明一个匿名函数,并且立即执行这个函数。函数中所有的代码都生活在一个闭包里,这个闭包让私有方法和成员成为现实,并且仅能够存在我们应用程序的整个生命周期。

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

请注意()包着的这个匿名函数。这是语言需要,如果没有()包着函数,javasript会认为这是一个函数声明。由于括弧()和JS的&&,异或,逗号等操作符是在函数表达式和函数声明上消除歧义的,所以一旦解释器知道其中一个是表达式,其他的也都默认成为了表达式。

2.全局引入

javascipt有一种特性叫隐式全局变量。就是当有一个变量被使用时,解释器会遍历作用域链来寻找var声明的这个变量,如果没有找到,这个变量就是默认成为了隐式全局变量。同时这也说明了在匿名闭包中创建全局变量也是非常容易的。不幸的是这样的做法让代码变得很难管理,因为我们很难知道哪个变量的声明周期是全局的。

幸运的是,我们的匿名函数提供了一个简单的选择。通过全局变量作为参数,通过这种方式引入全局变量比我们自己定义的隐式全局变量而言,更清晰更快速。这有一个例子:

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

现在很多类库里都有这种使用方式,比如jQuery源码。

3.模块导出

有时候,如果你不仅想传入全局变量 ,而且你想声明一些全局变量,我们这里提供了一个简单的方法来导出他们,而这个方法就是匿名函数的返回值。这样做就是最基本的模块模式。示例如下:

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

请注意,我们声明了一个全局的模块叫MODULE,它包含两个属性,一个成员变量moduleProperty和一个成员方法moduleMethod。除此之外,它还使用匿名函数的闭包维护了私有内部状态,我们也可以通过第二条规则轻松的传入我们需要传入的全局变量。

高级用法

上面的功能对我们日常开发来说已经很实用了,但是我们还是基于此模式可以设计出更强大,更易于拓展的结构。

1.扩展(Augmentation)

对于模块模式而言,唯一一条限制就是我们的模块的代码都只能在一个文件中声明。任何在大项目中干过得人都体会到分文件的好处。幸运的是我们有一个非常好的办法来解决这一瓶颈。

首先我们导入模块,然后添加属性,随后将其导出,如下面这个例子:

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

这里我们还是用var来保存这个返回值,虽然说这里不是必要。等解释玩段代码,新的方法会添加到MODULE中,这个拓文件中依然可以包含一些私有的方法和属性。

2.松耦合扩展(Loose Augmentation)

我们上面这个例子要求我们的模块被定义过,然后才有扩展。但这不总是必须的。对JavaScript而言,提高性能最好的方式就是异步加载js文件,我们在加载多个文件的时候希望能够解决加载顺序的问题,我们采用下面这种方式来声明所有的相同的模块:

var MODULE = (function (my) {
// add capabilities... return my;
}(MODULE || {}));//这是确保MODULE对象,在存在的时候直接用,不存在的时候直接赋值为{},

在这种模式下,用var声明每个模块是必须的,不然其他文件无法读取不到这个模块。

3.紧耦合扩展(Tight Augmentation)

虽然说松耦合扩展很棒,能够帮你分文件定义,但是他不能帮你实现重载的功能,也不能在模块初始化的时候使用模块的属性。紧模式帮你拓展了加载顺序限制,还提供了重载机制:

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

我们重载了moduleMethod方法,而且如果我们需要,可以将原来的方法保存了起来。

4.克隆与继承(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相应的function。不管怎样还是告诉大家这一点。

5.子模块(Sub-modules)

我们最后一个模式是这几个当中最简单的模式,我们有很多场景会用到它:

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

这其实也是module pattern中的高级用法之一,同时,我们也可以将上面的一些模式应用在子模块上。

总结

大多数高级用法可以和其他设计模式结合其他使用,如果对于设计复杂的项目,我个人倾向使用:松耦合扩展,私有状态和子模块三种模式。在这里我完全没有提到性能,但这里我还是提一下:这些模式对性能都有提升,松耦合允许并行下载,提高下载速度。代码初始化可能会比原来慢一点,但是由于全局变量的减少,子模块在获取局部变量的速度链变短,所有运行时JavaScript速度是会有显著提升。

(原文中高级模式中的多文件访问私有成员还未能理解,所以没有翻译,望请谅解)

本文翻译自:http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth

玩转JavaScript module pattern精髓的更多相关文章

  1. JavaScript module pattern精髓

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

  2. JavaScript Module Pattern: In-Depth

    2010-03-12 JavaScript Module Pattern: In-Depth The module pattern is a common JavaScript coding patt ...

  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. JavaScript Patterns 5.4 Module Pattern

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

  5. Learning JavaScript Design Patterns The Module Pattern

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

  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

    玩下Javascript 前言 好久没有更新博客了,也蛮少捣弄javascript,今儿看到一个题目,关于给你一个面板,你可以随意的在上面画矩形,可以移动和删除任意一个你创建的矩形,心血来潮搞着玩哈, ...

  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. e557. 在Applet中显示图片

    See also e551 精简的Applet. Image image; public void init() { // Load image image = getImage(getDocumen ...

  2. (转)音频输出PCM与LPCM有什么不同

     多声道LPCM:无损音轨原始存在格式,概念上等效于wave文件,并不需要运算解码,可直接输入功放进行DA转换,光纤和同轴接口只能传输2声道LPCM,多声道LPCM需要HDMI接口传输.   PCM: ...

  3. linux -- ubuntu14.10安装gnome桌面环境和dock工具

    ubuntu14.10系统自带Unity桌面环境,但是还是习惯于gnome桌面环境,再加上dock用着就很顺手了.   方法/步骤 首先,按Ctrl+Alt+T,调出终端.在其中输入:sudo apt ...

  4. [CS]C#操作word

    近期在做的项目已经改了好几版,近期这一版用到了word,当然不是直接使用word,而是使用第三方的ActiveX控件:dsoframer.ocx.此控件的使用和其它控件的使用流程没有不论什么差别.接下 ...

  5. CentOS7忘记root密码的解决方法

    开机启动centos 7.0,看到如下画面,选择下图选单,按"e"键 在下图linux16行中,将ro这两个字母修改为rw init=/sysroot/bin/sh 修改结果如下图 ...

  6. Linux下安装subversion1.6.5和apache2

    以下安装是在RHEL5.5默认安装的情况下,以root身份进行安装!这个实验我安装了n次,最后总是不成功,因为涉及到略多的软件和配置.下面是安装步骤和配置,自己记下来.希望给下次配置的时候不要像以前那 ...

  7. Linux 代理设置

    apt proxy vim /etc/apt/apt.conf Acquire::http::proxy "http://10.48.127.169:8080/"; Acquire ...

  8. 非常不错的前端框架Kendo-ui

    近期公司在做重构,准备换前端框架由Extjs换kendo-ui,问什么要换框架呢?主要有以下几个原因: Extjs太重,偏向后端语言,前端写起来费劲 Extjs执行太慢(这是主要原因),因为Extjs ...

  9. [dubbo] dubbo 基础使用

    Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案.简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候 ...

  10. JavaScript jQuery 笔记

    资料来源:http://www.w3school.com.cn/jquery/index.asp http://files.cnblogs.com/files/defineconst/jQuery.r ...