Javascript模块化编程详解


Posted in Javascript onDecember 01, 2014

模块化编程是一种非常常见Javascript编程模式。它一般来说可以使得代码更易于理解,但是有许多优秀的实践还没有广为人知。

基础

我们首先简单地概述一下,自从三年前Eric Miraglia(YUI的开发者)第一次发表博客描述模块化模式以来的一些模块化模式。如果你已经对于这些模块化模式非常熟悉了,大可以直接跳过本节,从“进阶模式”开始阅读。

匿名闭包

这是一种让一切变为可能的基本结构,同时它也是Javascript最棒的特性。我们将简单地创建一个匿名函数并立即执行它。所有的代码将跑在这个函数内,生存在一个提供私有化的闭包中,它足以使得这些闭包中的变量能够贯穿我们的应用的整个生命周期。

(function () {

    // ... all vars and functions are in this scope only

    // still maintains access to all globals

}());

注意这对包裹匿名函数的最外层括号。因为Javascript的语言特性,这对括号是必须的。在js中由关键词function开头的语句总是会被认为是函数声明式。把这段代码包裹在括号中就可以让解释器知道这是个函数表达式。

全局变量导入

Javascript有一个特性叫做隐式全局变量。无论一个变量名在哪儿被用到了,解释器会根据作用域链来反向找到这个变量的var声明语句。如果没有找到var声明语句,那么这个变量就会被视为全局变量。如果这个变量用在一句赋值语句中,同时这个变量又不存在时,就会创建出一个全局变量。这意味着在匿名闭包中使用或创建全局变量是很容易的。不幸的是,这会导致写出的代码极难维护,因为对于人的直观感受来说,一眼根本分不清那些是全局的变量。

幸运的是,我们的匿名函数提供了简单的变通方法。只要将全局变量作为参数传递到我们的匿名函数中,就可以得到比隐式全局变量更清晰又快速的代码了。下面是示例:

(function ($, YAHOO) {

    // now have access to globals jQuery (as $) and YAHOO in this code

}(jQuery, YAHOO));

模块导出

有时你不仅想要使用全局变量,你还想要声明它们,以供反复使用。我们可以很容易地通过导出它们来做到这一点——通过匿名函数的返回值。这样做将会完成一个基本的模块化模式雏形,接下来会是一个完整的例子:

var MODULE = (function () {

    var my = {},

        privateVariable = 1;

    function privateMethod() {

        // ...

    }

    my.moduleProperty = 1;

    my.moduleMethod = function () {

        // ...

    };

    return my;

}());

注意我们已经声明了一个叫做MODULE的全局模块,它拥有2个公有的属性:一个叫做MODULE.moduleMethod的方法和一个叫做MODULE.moduleProperty的变量。另外,它还维护了一个利用匿名函数闭包的、私有的内置状态。同时,我们可以很容易地导入需要的全局变量,并像之前我们所学到的那样来使用这个模块化模式。

进阶模式

上面一节所描述的基础已经足以应对许多情况,现在我们可以将这个模块化模式进一步的发展,创建更多强大的、可扩展的结构。让我们从MODULE模块开始,一一介绍这些进阶模式。

放大模式

整个模块必须在一个文件中是模块化模式的一个限制。任何一个参与大型项目的人都会明白将js拆分多个文件的价值。幸运的是,我们拥有一个很棒的实现来放大模块。首先,我们导入一个模块,并为它添加属性,最后再导出它。下面是一个例子——从原本的MODULE中放大它:

var MODULE = (function (my) {

    my.anotherMethod = function () {

        // added method...

    };

    return my;

}(MODULE));

我们用var关键词来保证一致性,虽然它在此处不是必须的。在这段代码执行完之后,我们的模块就已经拥有了一个新的、叫做MODULE.anotherMethod的公有方法。这个放大文件也会维护它自己的私有内置状态和导入的对象。

宽放大模式

我们的上面例子需要我们的初始化模块最先被执行,然后放大模块才能执行,当然有时这可能也不一定是必需的。Javascript应用可以做到的、用来提升性能的、最棒的事之一就是异步执行脚本。我们可以创建灵活的多部分模块并通过宽放大模式使它们可以以任意顺序加载。每一个文件都需要按下面的结构组织:

var MODULE = (function (my) {

    // add capabilities...

    return my;

}(MODULE || {}));

在这个模式中,var表达式使必需的。注意如果MODULE还未初始化过,这句导入语句会创建MODULE。这意味着你可以用一个像LABjs的工具来并行加载你所有的模块文件,而不会被阻塞。

紧放大模式

宽放大模式非常不错,但它也会给你的模块带来一些限制。最重要的是,你不能安全地覆盖模块的属性。你也无法在初始化的时候,使用其他文件中的属性(但你可以在运行的时候用)。紧放大模式包含了一个加载的顺序序列,并且允许覆盖属性。这儿是一个简单的例子(放大我们的原始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));

我们在上面的例子中覆盖了MODULE.moduleMethod的实现,但在需要的时候,可以维护一个对原来方法的引用。

克隆与继承

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));

这个模式可能是最缺乏灵活性的一种选择了。它确实使得代码显得很整洁,但那是用灵活性的代价换来的。正如我上面写的这段代码,如果某个属性是对象或者函数,它将不会被复制,而是会成为这个对象或函数的第二个引用。修改了其中的某一个就会同时修改另一个(译者注:因为它们根本就是一个啊!)。这可以通过递归克隆过程来解决这个对象克隆问题,但函数克隆可能无法解决,也许用eval可以解决吧。因此,我在这篇文章中讲述这个方法仅仅是考虑到文章的完整性。

跨文件私有变量

把一个模块分到多个文件中有一个重大的限制:每一个文件都维护了各自的私有变量,并且无法访问到其他文件的私有变量。但这个问题是可以解决的。这里有一个维护跨文件私有变量的、宽放大模块的例子:

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 || {}));

所有文件可以在它们各自的_private变量上设置属性,并且它理解可以被其他文件访问。一旦这个模块加载完成,应用程序可以调用MODULE._seal()来防止外部对内部_private的调用。如果这个模块需要被重新放大,在任何一个文件中的内部方法可以在加载新的文件前调用_unseal(),并在新文件执行好以后再次调用_seal()。我如今在工作中使用这种模式,而且我在其他地方还没有见过这种方法。我觉得这是一种非常有用的模式,很值得就这个模式本身写一篇文章。

子模块

我们的最后一种进阶模式是显而易见最简单的。创建子模块有许多优秀的实例。这就像是创建一般的模块一样:

MODULE.sub = (function () {

    var my = {};

    // ...

    return my;

}());

虽然这看上去很简单,但我觉得还是值得在这里提一提。子模块拥有一切一般模块的进阶优势,包括了放大模式和私有化状态。

结论

大多数进阶模式可以结合到一起来创建一个更为有用的模式。如果实在要我推荐一种设计复杂应用程序的模块化模式的化,我会选择结合宽放大模式、私有变量和子模块。

我还未考虑过这些模式的性能问题,但我宁愿把这转化为一个更简单的思考方式:如果一个模块化模式有很好的性能,那么它能够把最小化做的很棒,使得下载这个脚本文件更快。使用宽放大模式可以允许简单的非阻塞并行下载,这就会加快下载速度。初始化时间可能会稍慢于其他方法,但权衡利弊后这还是值得的。只要全局变量导入准确,运行时性能应该会不会受到影响,而且还有可能在子模块中通过用私有变量缩短引用链来得到更快的运行速度。

作为结束,这里是一个子模块动态地把自身加载到它的父模块的例子(如果父模块不存在则创建它)。为了简洁,我把私有变量给去除了,当然加上私有变量也是很简单的啦。这种编程模式允许一整个复杂层级结构代码库通过子模块并行地完成加载。

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));

本文总结了当前"Javascript模块化编程"的最佳实践,说明如何投入实用。虽然这不是初级教程,但是只要稍稍了解Javascript的基本语法,就能看懂。

Javascript 相关文章推荐
js调用flash的效果代码
Apr 26 Javascript
javascript getElementsByName()的用法说明
Jul 31 Javascript
php上传图片并给图片打上透明水印的代码
Jun 07 Javascript
限制textbox或textarea输入字符长度的JS代码
Oct 16 Javascript
jquery实现页面百叶窗走马灯式翻滚显示效果的方法
Mar 12 Javascript
jQuery validate插件实现ajax验证重复的2种方法
Jan 22 Javascript
微信小程序 限制1M的瘦身技巧与方法详解
Jan 06 Javascript
Vue Router history模式的配置方法及其原理
May 30 Javascript
Vue+axios+WebApi+NPOI导出Excel文件实例方法
Jun 05 Javascript
微信小程序实现打卡签到页面
Sep 21 Javascript
vue添加自定义右键菜单的完整实例
Dec 08 Vue.js
vue keep-alive的简单总结
Jan 25 Vue.js
浅谈Javascript中深复制
Dec 01 #Javascript
使用JS+plupload直接批量上传图片到又拍云
Dec 01 #Javascript
关于javascript模块加载技术的一些思考
Nov 28 #Javascript
jQuery实现Twitter的自动文字补齐特效
Nov 28 #Javascript
关于编写性能高效的javascript事件的技术
Nov 28 #Javascript
推荐25个超炫的jQuery网格插件
Nov 28 #Javascript
实例分析javascript中的call()和apply()方法
Nov 28 #Javascript
You might like
php使用curl通过代理获取数据的实现方法
2016/05/16 PHP
PHPCMS2008广告模板SQL注入漏洞修复
2016/10/11 PHP
js 图片轮播(5张图片)
2008/12/30 Javascript
javascript 模式设计之工厂模式详细说明
2010/05/10 Javascript
JS中confirm,alert,prompt函数区别分析
2011/01/17 Javascript
jQuery参数列表集合
2011/04/06 Javascript
IE与Firefox在JavaScript上的7个不同句法分享
2011/10/30 Javascript
IE、FF浏览器下修改标签透明度
2014/01/28 Javascript
js解析json读取List中的实体对象示例
2014/03/11 Javascript
Angular.js中用ng-repeat-start实现自定义显示
2016/10/18 Javascript
JavaScript判断浏览器对CSS3属性是否支持的多种方法
2016/11/13 Javascript
JS使用cookie实现只出现一次的广告代码效果
2017/04/22 Javascript
vue实现微信分享链接添加动态参数的方法
2019/04/29 Javascript
angularjs模态框的使用代码实例
2019/12/20 Javascript
深入理解Antd-Select组件的用法
2020/02/25 Javascript
nodejs使用Sequelize框架操作数据库的实现
2020/10/21 NodeJs
vue vant中picker组件的使用
2020/11/03 Javascript
[01:14:30]TNC vs VG 2019国际邀请赛淘汰赛 胜者组赛BO3 第二场 8.20.mp4
2019/08/22 DOTA
[55:45]LGD vs OG 2019国际邀请赛淘汰赛 胜者组 BO3 第三场 8.24
2019/09/10 DOTA
跟老齐学Python之??碌某?? target=
2014/09/12 Python
Linux上安装Python的PIL和Pillow库处理图片的实例教程
2016/06/23 Python
Python中的错误和异常处理简单操作示例【try-except用法】
2017/07/25 Python
Pandas中DataFrame的分组/分割/合并的实现
2019/07/16 Python
Python基于数列实现购物车程序过程详解
2020/06/09 Python
Booking.com美国:全球酒店预订网站
2017/04/18 全球购物
美国紧身牛仔裤品牌:NYDJ
2017/05/24 全球购物
澳洲本土太阳镜品牌:Quay Australia
2019/07/29 全球购物
打架检讨书500字
2014/01/29 职场文书
学校校庆演讲稿
2014/05/22 职场文书
部门活动策划方案
2014/08/16 职场文书
我们的节日元宵活动方案
2014/08/23 职场文书
红领巾心向党演讲稿
2014/09/10 职场文书
2015年团支部年度工作总结
2015/05/27 职场文书
茶花女读书笔记
2015/06/29 职场文书
大学毕业谢师宴致辞
2015/07/27 职场文书
js面向对象编程OOP及函数式编程FP区别
2022/07/07 Javascript