前言

模块是任何大型应用程序架构中不可缺少的一部分,模块可以使我们清晰地分离和组织项目中的代码单元。在项目开发中,通过移除依赖,松耦合可以使应用程序的可维护性更强。与其他传统编程语言不同,在当前JavaScript里,并没有提供原生的、有组织性的引入模块方式。本文就来探讨一下目前的常见几种模块化解决方案。

1.对象字面量表示法

对象字面量可以认为是包含一组键值对的对象,每一对键和值由冒号分隔。对象字面量不需要使用new运算符进行实例化,在对象的外部也可以给对象添加属性和方法。示例如下:

 var myModule = {
myProperty: "jeri", // 对象字面量可以包含属性和方法
// 例如,可以声明模块的配置对象
myConfig: {
useCaching: true,
language: "en"
}, // 基本方法
myMethod1: function () {
console.log("method1");
}, // 根据当前配置输出信息
myMethod2: function () {
console.log("Caching is:" + '(this.myConfig.useCaching) ? "enabled" : "disabled"');
}, // 根据当前配置输出信息
myMethod3: function (newConfig) { if (typeof newConfig === "object") {
this.myConfig = newConfig;
console.log(this.myConfig.language);
}
}
}

如上所述,使用对象字面量有助于封装和组织代码,然后不同的对象字面量模块再构成复杂的项目。

2.Module模式

Module模式最初定义在传统的软件工程中,为类提供私有和公有封装的方法。在JavaScript中,并不能可以直接声明类,但我们可以使用闭包来封装私有的属性和方法,进而模拟类的概念,在JavaScript中实现Module模式。通过这种方式,我们就使得一个单独的对象拥有公有/私有方法和变量,从而屏蔽来自全局作用域的特殊部分,也就大大降低了变量声明之间和函数声明之间冲突的可能性。

 var myModule = (function () {

     // 私有变量
var privateVar = 0; // 私有函数
var privateFun = function (foo) {
console.log(foo);
}; return {
// 私有变量
publicVar: "foo", // 公有函数
publicFun: function (arg) { // 修改私有变量
privateVar ++; // 传入bar调用私有方法
privateFun(arg);
}
};
}) ();

如上所示,通过使用闭包我们封装了私有变量和方法,而只暴露了一个接口供其他部分调用。私有变量(privateVar)和方法(privateFun)被局限于模块的闭包之中,只有通过公有方法才能访问。该模式除了返回的是一个对象而不是一个函数之外,非常类似于一个立即调用函数表达式,我们可以为返回的对象添加新的属性和方法,这些新增的属性和方法对外部调用者来说都是可用的。

Module模式的这种JavaScript实现对于具有面向对象开发经验的人来说非常简洁,但其也有自身的缺点和劣势。

由于我们访问公有和私有成员的方式不同,当我们想改变可见性时,我们需要修改每一个曾经使用该成员的地方,并不利于维护和升级,耦合度并不理想。而且,在之后新添加的方法里,我们并不能访问以前声明的私有方法和变量,因为闭包只在创建时完成绑定。我们也无法为私有方法创建自动化单元测试,修正私有方法也是极其困难的,我们需要复写所有与私有方法交互的公有方法,bug修正时工作量会很大。另外,我们也不能轻易的扩展私有方法。

关于脚本加载器

要讨论 AMD 和 CommonJS 模块,我们必然会谈及一个显而易见的话题——脚本加载器。目前,脚本加载是为了让我们能在现今的各种应用中都能使用模块化的 JavaScript 这个目标而服务的。有很多加载器用于 AMD 和 CommonJS方式中的模块加载,比较出名的有RequireJS 和 curl.js。关于脚本加载器的使用方式和运行机制,大家可以自行了解一下。

3.AMD模块

AMD全称是Asynchronous Module Definition,即异步模块加载机制。它诞生于使用XHR+eval的Dojo开发经验,其整体目标是提供模块化的JavaScript解决方案,避免未来的任何解决方案受到过去解决方案缺点的影响。AMD模块格式本身就是对定义模块的建议,其模块和依赖都可以进行异步加载,而且具有高度的灵活性,清除了代码和模块之间可能惯有的紧耦合。

关于AMD有两个非常重要的概念,那就是用于模块定义的define方法和用于处理依赖加载的require方法。

作为一个规范,只需定义其语法API,而不关心其实现。define函数定义如下:
 define(
[module-name?] /*可选*/,
[array-of-dependencies?] /*可选*/,
[module-factory-or-object]
);
其中:
  • module-name: 模块标识,可以省略。如果没有这个属性,则称为匿名模块。
  • array-of-dependencies: 所依赖的模块,可以省略。
  • module-factory-or-object: 模块的实现,或者一个JavaScript对象。

具体示例如下:

 define(
"myModule",
["foo", "bar"], // 模块定义函数,依赖(foo,bar)作为参数映射到函数上
function (foo, bar) {
// 创建模块
var myModule = {
myFun: function () {
console.log("Jeri");
}
} // 返回定义的模块
return myModule;
}
);

require用于加载JavaScript文件或模块的代码,获取依赖。示例如下:

 // foo,bar为外部模块,加载以后的输出作为回调函数的参数传入,以便访问
requrie(["foo", "bar"], function (foo, bar) { // 其他代码
foo.doSomething();
});

下面是一个动态加载依赖的示例:

 define(
function (requrie) {
var isReady = false,
foobar; requrie(["foo", "bar"], function (foo, bar) {
isReady = true,
foobar = foo() + bar();
}); // 返回定义的模块
return {
isReady: isReady,
foobar: foobar
};
}
);

AMD模块可以使用插件,也就是说当我们加载依赖时,可以加载任意格式的文件。AMD对于如何完成灵活模块的定义提供了明确的建议,使用AMD编写模块化的JS代码,比现有的全局命名空间和<script>标签解决方案更加简洁,没有全局命名空间污染,在需要的时候也可以延迟加载脚本。

4.CommonJS模块

CommonJS规范建议指定一个简单的API来声明在浏览器外部工作的模块。与AMD不同,它试图包含更广泛的引人关注的问题,如IO、文件系统等。

从结构来看,CommonJS模块是JS中可以复用的部分,导出特定对象,以便可以用于任何依赖代码。与AMD表现形式不同的是,CommonJS模块并不使用define进行定义。CommonJS模块由两部分组成:变量exports和require函数。exports包含了一个模块希望其他模块能够使用的对象,require函数用来导入其他模块的导出,也就是用来加载其他模块依赖。示例如下:

lib.js

 // 新定义的模块方法
function log(arg) {
console.log(arg);
} // 把方法暴露给其他模块
exports.log = log;

app.js

 // ./lib是我们需要的一个依赖
var lib = requrie("./lib"); // 新定义的模块方法
function foo() {
lib.log("jeri");
} // 把方法暴露给其他模块
exports.foo = foo;

虽然在浏览器端可以使用CommonJS组织模块,但有不少开发者认为CommonJS更适合于服务器端开发,因为很多CommonJS API具有面向服务器的特性,如io、system等。NodeJs使用的就是CommonJS规范。当一个模块可能用于服务器端时,一些开发人员倾向于选择CommonJS,其他情况下使用AMD。

AMD模块可以使用插件,也就是说当我们加载依赖时,可以加载任意格式的文件,并且可以定义更细粒度的东西,如构造函数和函数,但CommonJS模块仅能定义不易使用的对象。在模块的定义和引入方面,二者也有很大的不同。AMD和CommonJS都是非常优秀的模块模式,各自有不同的目标。

  • AMD采用采用浏览器优先的开发方法,选择异步行为和简化的后向兼容性,但没有任何的文件I/O概念。支持对象、函数、构造函数以及其他类型的对象,在浏览器中原生运行。
  • CommonJS采用服务器优先的方法,假定同步行为,没有全局概念负担,仅将对象作为模块给予支持。CommonJS支持非包装模块,更接近下一代ES Harmony规范。

5.ES Harmony模块

TC39——负责制定 ECMAScript 语法和语义以及其未来迭代的标准团体,在近几年一直在密切关注 JavaScript 在大规模开发中的使用情况的演进,而且也敏感地意识到了需要有更好的语言特性来编写更加模块化的 JS。基于这个原因,目前有提案已经提出了一系列令人振奋的对语言的补充。虽然Harmony还处于建议阶段,但我们可以先一览新的接口特性。

Imports和Exports模块

在ES.next中,已经为模块依赖和模块导出提供了更加简洁的方式,那就是import和export。

  • import声明绑定一个模块,作为局部变量导出,并能被重命名,以避免名称冲突。
  • export声明一个外部可见模块的本地绑定,其它模块能够读取这些导出,但无法进行修改。模块可以导出子模块,但不能导出再其他地方定义的模块。导出也是可以重命名的。

cakes.js

 module staff {
// 指定导出
export var baker = {
bake: function(item) {
console.log('Woo! I just baked ' + item);
}
}
}; module skills {
export var specialty = "baking";
export var experience = "5 years";
}; module cakeFactory {
// 指定依赖项
import baker from staff; // 通过通配符导入所有东西
import * from skills; export var oven = {
makeCupcake: function(toppings) {
baker.bake('cupcake', toppings);
},
makeMuffin: function(mSize) {
baker.bake('muffin', size);
}
}
};

远程加载模块

在ES.next里还建议支持远程模块加载,示例如下:

 module cakeFactory from 'http://****/cakes.js';

 cakeFactory.oven.makeCupcake('sprinkles');

 cakeFactory.oven.makeMuffin('large');

模块加载器 API

模块加载器建议一个动态的API在严格控制的上下问中加载模块。加载器支持的特征包括用来加载模块的load( url, moduleInstance, error),以及createModule( object, globalModuleReferences)等等。

用于服务器的类 CommonJS 模块

对于面向服务器的开发者来说,在 ES.next 中提出的模块系统并非局限于对浏览器端模块的关注。例如下面是一个在服务器端使用的类CommonJS模块:

File.js

 // io/File.js
export function open(path) {
// ...
};
export function close(hnd) {
// ...
};

LexicalHandler.js

 // compiler/LexicalHandler.js
module file from 'io/File'; import {open, close} from file;
export function scan( in ) {
try {
var h = open( in )...
} finally {
close(h)
}
}

app.js

 module lexer from 'compiler/LexicalHandler';
module stdlib from '@std'; // ... scan(cmdline[0]) ...

ES Harmony有了很多令人振奋的新功能加入,以求简化应用程序的开发,并处理依赖管理等问题。然而,至今为止,还没有形成新的规范,并不能得到众多浏览器的支持。目前,要想使用Harmony语法的最佳选择是通过transpiler,如谷歌的Traceur或Esprima。在新规范发布之前,我们还是选择AMD和CommonJS较为稳妥。

写在最后

本文论述了几种模块化编程的方式,它们各有优劣,各有适用场景。希望在以后的编程实践中,选择合适的模式,不断提高代码的可读性、可维护性和可扩展性。

JavaScript之模块化编程的更多相关文章

  1. Javascript的模块化编程

    随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂. 网页越来越像桌面程序,需要一个团队分工协作.进度管理.单元测试等等......开发者 ...

  2. Javascript 的模块化编程及加载模块【转载+整理】

    http://www.ruanyifeng.com/blog/2012/10/javascript_module.html 本文内容 引入 模块化 最初写法 对象写法 立即执行函数写法 放大模式 宽放 ...

  3. 学习了一下javascript的模块化编程

    现在在我脑海里关于“模块化”的概念是这些词:简单.具有逻辑之美.易用.健壮.可扩展.似乎这些形容与我现在水平写出的代码有点格格不入啊. 所以今天想了解和简单的实践一下“模块化开发”. 1.首先学习一下 ...

  4. 应用require.js进行javascript模块化编程小试一例

    长久以来都渴望应用javascript的模块化编程.今日紧迫更甚,岁月蹉跎,已经不能再等了. 拜读阮一峰的有关文章已经好几遍,文章写得真好,简洁流畅,头头是道,自觉有点明白了.但经验告诉我们,一定要亲 ...

  5. javascript模块化编程:CommonJS和AMD规范

    AMD规范,异步模块定义.与CommonJS规范齐名并列. 作用都是利于JavaScript的模块化编程. 模块化编程的好处就是: 1.可重用 2.独立 3.能解决加载的依赖性问题 4.能解决重复加载 ...

  6. Javascript模块化编程(三):require.js的用法

    Javascript模块化编程(三):require.js的用法 原文地址:http://www.ruanyifeng.com/blog/2012/11/require_js.html 作者: 阮一峰 ...

  7. Javascript模块化编程(二):AMD规范

    Javascript模块化编程(二):AMD规范   作者: 阮一峰 原文地址:http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_d ...

  8. Javascript模块化编程(一):模块的写法

    Javascript模块化编程(一):模块的写法 作者: 阮一峰 原文链接:http://www.ruanyifeng.com/blog/2012/10/javascript_module.html ...

  9. Javascript模块化编程(二):AMD规范(转)

    这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要 ...

随机推荐

  1. [SAP ABAP开发技术总结]FTP到文件服务器,服务器上文件读写

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  2. JavaScript经典代码【一】【javascript HTML控件获取值】

    javascript HTML控件获取值 1.下拉列表框选定值 ddlPageSize.options[ddlPageSize.selectedIndex].value ddlPageSize.opt ...

  3. Spring的DI(Ioc) - 注入bean 和 基本数据类型

    注入bean有两种方式: 注入其他bean: 方式一 <bean id="orderDao" class="cn.itcast.service.OrderDaoBe ...

  4. 一个封装较好的删除方法(Delete)

    前台的引用 @Html.ActionLink(“删除字样”,“后台的删除方法”,new{绑定id},new{@style="样式"});方法,如何要独立使用的话,一般还要使用到相应 ...

  5. 手机CPU知识扫盲:谈谈手机CPU架构与原理 (全

    CPU是手机上面最复杂,最贵的Soc(芯片),担任的也是手机中大脑的位 置,是手机跑分性能的决定性硬件.智能手机发展到今天,各大手机CPU厂商也从春秋战国逐渐到了现在四国鼎立的时代(高通,MTK,三星 ...

  6. Linux 下操作GPIO(两种方法,驱动和mmap)(转载)

    目前我所知道的在Linux下操作GPIO有两种方法: 1.编写驱动,这当然要熟悉Linux下驱动的编写方法和技巧,在驱动里可以使用ioremap函数获得GPIO物理基地址指针,然后使用这个指针根据io ...

  7. phpStorm如何在Console控制台执行php文本,而不是浏览器中

    如何在Console控制台执行php文本 phpStorm默认会在浏览器中执行脚本 默认的配置 配置PHP脚本 扩展,配置项目运行

  8. js数组知识

    js数组   shift:删除原数组第一项,并返回删除元素的值:如果数组为空则返回undefined var a = [1,2,3,4,5]; var b = a.shift(); //a:[2,3, ...

  9. flexbox弹性伸缩布局

    <!doctype html><html lang="en"><head> <meta charset="UTF-8" ...

  10. struts2 详解

    Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互.struts使 ...