深切明白JavaScript系列3:周全剖析Module形式

简介

Module形式是JavaScript编程中一个异常通用的形式,平常情况下,人人都晓得基本用法,本文尝试着给人人更多该形式的高等运用体式格局。

起首我们来看看Module形式的基本特征:

  1. 模块化,可重用

  2. 封装了变量和function,和全局的namaspace不打仗,解耦合

  3. 只暴露可用public的要领,别的私有要领悉数隐蔽

关于Module形式,最早是由YUI的成员Eric Miraglia在2007年摆布(原文为4年前,文章宣布日期为2011年所以我算了一下是4年前)提出了这个观点,我们将从一个简朴的例子来诠释一下基本的用法(假如你已异常熟习了,请疏忽这一节)。

基本用法

先看一下最简朴的一个完成,代码以下:

var Calculator = function (eq) {
    //这里能够声明私有成员

    var eqCtl = document.getElementById(eq);

    return {
        // 暴露公然的成员
        add: function (x, y) {
            var val = x + y;
            eqCtl.innerHTML = val;
        }
    };
};

我们能够经由过程以下的体式格局来挪用:

var calculator = new Calculator('eq');
calculator.add(2, 2);

人人能够看到了,每次用的时刻都要new一下,也就是说每一个实例在内存里都是一份copy,假如你不须要传参数或许没有一些特别刻薄的请求的话,我们能够在末了一个}背面加上一个括号,来到达自实行的目标,如许该实例在内存中只会存在一份copy,不过在展现他的长处之前,我们照样先来看看这个形式的基本运用要领吧。

匿名闭包

==匿名闭包==是让统统成为能够的基本,而这也是JavaScript最好的特征,我们来建立一个最简朴的闭包函数,函数内部的代码一向存在于闭包内,在全部运转周期内,该闭包都保证了内部的代码处于私有状况。

(function () {
    // ... 一切的变量和function都在这里声明,而且作用域也只能在这个匿名闭包里
    // ...然则这里的代码依旧能够接见外部全局的对象
}());

注重,匿名函数背面的括号,这是JavaScript言语所请求的,由于假如你不声明的话,JavaScript诠释器默许是声明一个function函数,有括号,就是建立一个函数表达式,也就是自实行,用的时刻不必和上面那样在new了,固然你也能够如许来声明:

(function () {/* 内部代码 */})();

不过我们引荐运用第一种体式格局,关于函数自实行,我背面会有特地一篇文章举行详解,这里就不多说了。

援用全局变量

JavaScript(非严厉形式下)有一个特征叫做隐式全局变量,不论一个变量有无用过,JavaScript诠释器反向遍历作用域链来查找全部变量的var声明,假如没有找到var,诠释器则假定该变量是全局变量,假如该变量用于了赋值操纵的话,之前假如不存在的话,诠释器则会自动建立它,这就是说在匿名闭包里运用或建立全局变量异常轻易,不过比较难题的是,代码比较难管理,尤其是浏览代码的人看着许多辨别哪些变量是全局的,哪些是部分的。

不过,好在在匿名函数里我们能够供应一个比较简朴的替换计划,我们能够将全局变量当做一个参数传入到匿名函数然后运用,比拟隐式全局变量,它又清楚又快,我们来看一个例子:

(function ($, YAHOO) {
    // 这里,我们的代码就能够运用全局的jQuery对象了,YAHOO也是一样
} (jQuery, YAHOO));

如今许多类库里都有这类运用体式格局,比方jQuery源码。

不过,有时刻能够不单单议要运用全局变量,而是也想声明全局变量,怎样做呢?我们能够经由过程匿名函数的返回值来返回这个全局变量,这也就是一个基本的Module形式,来看一个完全的代码:

    var blogModule = (function () {
    var my = {}, privateName = "segmentfault";

    function privateAddTopic(data) {
        // 这里是内部处置惩罚代码
    }

    my.Name = privateName;
    my.AddTopic = function (data) {
        privateAddTopic(data);
    };

    return my;
} ());

上面的代码声清楚明了一个全局变量blogModule,而且带有2个可接见的属性:blogModule.AddTopic和blogModule.Name,除此之外,别的代码都在匿名函数的闭包里保持着私有状况。同时依据上面传入全局变量的例子,我们也能够很方便地传入别的的全局变量。

高等用法

上面的内容对大多数用户已很足够了,但我们还能够基于此形式延伸出更壮大,易于扩大的构造,让我们一个一个来看。

扩大

==Module形式==的一个限定就是一切的代码都要写在一个文件,然则在一些大型项目里,将一个功用星散成多个文件是异常重要的,由于能够多人协作易于开辟。再回头看看上面的全局参数导入例子,我们可否把blogModule本身传进去呢?答案是一定的,我们先将blogModule传进去,增加一个函数属性,然后再返回就到达了我们所说的目标,上代码:

var blogModule = (function (my) {
    my.AddPhoto = function () {
        //增加内部代码
    };
    return my;
} (blogModule));

这段代码,看起来是否是有C#里扩大要领的觉得?有点相似,但实质不一样哦。同时只管var不是必需的,但为了确保一致,我们再次运用了它,代码实行今后,blogModule下的AddPhoto就能够运用了,同时匿名函数内部的代码也依旧保证了私密性和内部状况。

松耦合扩大

上面的代码只管能够实行,然则必需先声明blogModule,然后再实行上面的扩大代码,也就是说步骤不能乱,怎样处理这个题目呢?我们来追念一下,我们日常平凡声明变量的都是都是如许的:

var cnblogs = cnblogs || {} ;

这是确保cnblogs对象在存在的时刻直接用,不存在的时刻直接赋值为{},我们来看看怎样运用这个特征来完成Module形式的恣意加载递次:

var blogModule = (function (my) {
    // 增加一些功用
    return my;
} (blogModule || {}));

经由过程如许的代码,每一个零丁星散的文件都保证这个构造,那末我们就能够完成恣意递次的加载,所以,这个时刻的var就是必须要声明的,由于不声明,别的文件读取不到哦。

紧耦合扩大

虽然松耦合扩大很牛叉了,然则能够也会存在一些限定,比方你没办法重写你的一些属性或许函数,也不能在初始化的时刻就是用Module的属性。紧耦合扩大限定了加载递次,然则供应了我们重载的时机,看以下例子:

var blogModule = (function (my) {
    var oldAddPhotoMethod = my.AddPhoto;

    my.AddPhoto = function () {
        // 重载要领,依旧可经由过程oldAddPhotoMethod挪用旧的要领
    };

    return my;
} (blogModule));

经由过程这类体式格局,我们到达了重载的目标,固然假如你想在继续在内部运用原有的属性,你能够挪用oldAddPhotoMethod来用。

克隆与继续

var blogModule = (function (old) {
    var my = {},
        key;

    for (key in old) {
        if (old.hasOwnProperty(key)) {
            my[key] = old[key];
        }
    }

    var oldAddPhotoMethod = old.AddPhoto;
    my.AddPhoto = function () {
        // 克隆今后,举行了重写,固然也能够继续挪用oldAddPhotoMethod
    };

    return my;
} (blogModule));

这类体式格局天真是天真,然则也须要消费天真的价值,实在该对象的属性对象或function基础没有被复制,只是对同一个对象多了一种援用罢了,所以假如老对象去转变它,那克隆今后的对象所具有的属性或function函数也会被转变,处理这个题目,我们就得是用递归,但递归对function函数的赋值也不好用,所以我们在递归的时刻eval响应的function。不论怎样,我照样把这一个体式格局放在这个帖子里了,人人运用的时刻注重一下就好了。

跨文件同享私有对象

经由过程上面的例子,我们晓得,假如一个module分割到多个文件的话,每一个文件须要保证一样的构造,也就是说每一个文件匿名函数里的私有对象都不能交织接见,那假如我们非要运用,那怎样办呢? 我们先看一段代码:

var blogModule = (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;
        };
    return my;
} (blogModule || {}));

任何文件都能够对他们的部分变量_private设属性,而且设置对其他的文件也马上见效。一旦这个模块加载完毕,运用会挪用 blogModule._seal()”上锁”,这会阻挠外部接入内部的_private。假如这个模块须要再次增生,运用的生命周期内,任何文件都能够挪用_unseal() ”开锁”,然后再加载新文件。加载后再次挪用 _seal()”上锁”。

子模块

末了一个也是最简朴的运用体式格局,那就是建立子模块

blogModule.CommentSubModule = (function () {
    var my = {};
    // ...

    return my;
} ());

只管异常简朴,我照样把它放进来了,由于我想申明的是子模块也具有平常模块一切的高等运用体式格局,也就是说你能够对恣意子模块再次运用上面的一些运用要领。

总结

上面的大部分体式格局都能够相互组合运用的,平常来说假如要设想体系,能够会用到松耦合扩大,私有状况和子模块如许的体式格局。别的,我这里没有提到机能题目,但我以为Module形式效率高,代码少,加载速率快。运用松耦合扩大许可并行加载,这更能够提拔下载速率。不过初始化时候能够要慢一些,然则为了运用好的形式,这是值得的。

参考文章:
http://yuiblog.com/blog/2007/06/12/module-pattern/
http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth

关于本文

本文转自TOM大叔深切明白JavaScript系列

【深切明白JavaScript系列】文章,包含了原创,翻译,转载,整顿等各范例文章,原文是TOM大叔的一个异常不错的专题,现将其重新整顿宣布。感谢大叔。

    原文作者:hiyangguo
    原文地址: https://segmentfault.com/a/1190000004262443
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞