玩转JavaScript module pattern精髓

  JavaScript module pattern是一种常见的javascript编码模式。这种模式自己很好理解,可是有不少高级用法尚未获得你们的注意。本文,咱们将回顾这种设计模式,而且介绍一些高级的用法,其中一个是我原创的。javascript

个人问题

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

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声明的这个变量,若是没有找到,这个变量就是默认成为了隐式全局变量。同时这也说明了在匿名闭包中建立全局变量也是很是容易的。不幸的是这样的作法让代码变得很难管理,由于咱们很难知道哪一个变量的声明周期是全局的。异步

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

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

  如今不少类库里都有这种使用方式,好比jQuery源码。ide

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

相关文章
相关标签/搜索