javascript模块化的初步探索

什么是javascript模块化

现在的前端开发, 愈来愈趋向于桌面应用,需要团队合作,管理。开发一个新的页面,我们可能需要加载其他别人写好的模块,这个时候,我们就需要javascript模块化。
模块:简单来说就是实现特定功能的一组方法。

对于javascript模块化编程,先辈们已经为我们给予我们很大的帮助,这里对于模块编程的思维总结我们不做太多的介绍,对于模块化还不太清晰的可以先看阮一峰 老师的这篇博客,Javascript模块化编程(一):模块的写法

下面介绍当前对于javascript模块化实现比较完善的相关知识。

当前主流的javascript模块化实现

说到当前对于javascript模块化实现比较完善,理所当然我们会想到,common.js,AMD,CMD,ES6的Module。

CommonJS

CommonJS帮助JS实现模块的功能。
eg:现在很热门的Node.js就是CommonJS规范的一个实现。

CommonJS在模块中定义方法要借助一个全局变量exports,它用来生成当前模块的API:

1
2
3
4
5
/* math module */

exports.add = function(a, b){
return a + b;
};

要加载模块就要使用CommonJS的一个全局方法require()。加载之前实现的math模块像这样:

1
var math = require('math');

加载后math变量就是这个模块对象的一个引用,要调用模块中的方法就像调用普通对象的方法一样了:

1
2
var math = require('math');
math.add(1, 3);

总之,CommonJS就是一个模块加载器,可以方便地对JavaScript代码进行模块化管理。

但是其实CommonJS也有一个很大的弊端:
浏览器除了本地缓存的资源,所有的资源都必须从服务器下载下来,这个加载的时间也需要根据当时的网速而定,而commonJS的加载是同步阻塞的,必须要加载完上一个才能继续下一个,就导致,如果中间有个文件是很大的,那么后面的就必须要停止,等待前面的加载完成,才能继续。

AMD

AMD 全称 The Asynchronous Module Definition , AMD规范也就是“异步模块定义规范”,是 RequireJS在推广过程中对模块定义的规范化产出。

AMD制定了定义模块的规则,这样模块和模块的依赖可以被异步加载。这和浏览器的异步加载模块的环境刚好适应(浏览器同步加载模块会导致性能、可用性、调试和跨域访问等问题)。

规范只定义了一个函数 “define”,它是全局变量。函数的描述为:
define(id?, dependencies?, factory);
用AMD规范实现一个简单的模块可以这样

创建一个名为”alpha”的模块,使用了require,exports,和名为”beta”的模块:

1
2
3
4
5
6
7
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
returnrequire("beta").verb();
}
});

这里只是简单的介绍,有兴趣的学习和使用AMD,可以去看以下的一些资料:

  1. AMD in github)
  2. 阮一峰:AMD规范

CMD

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。该规范明确了模块的基本书写格式和基本交互规则。
在 CMD 规范中,一个模块就是一个文件。代码的书写格式如下:

1
define(factory);

具体的一些规范可以在以下链接可看:

  1. CMD规范 in github

AMD 和 CMD 的区别

笔者自认为对ADM和CMD的学习和深入并不多,所以在这里拾人牙慧,以下区别的相关内容引用于知乎的一个答案。
作者:玉伯
链接:https://www.zhihu.com/question/20351507/answer/14859415

  1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.

  2. CMD 推崇依赖就近,AMD 推崇依赖前置。看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})

虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。

  1. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。
  1. 还有一些细节差异,具体看这个规范的定义就好,就不多说了。

    AMD 规范在这里:https://github.com/amdjs/amdjs-api/wiki/AMD
    CMD 规范在这里:https://github.com/seajs/seajs/issues/242
    另外,SeaJS 和 RequireJS的差异,可以参考:https://github.com/seajs/seajs/issues/277

ES6的module

在ES6之前,javascript一直没有模块体系。只有一些模块加载方案,CommonJS和AMD,CMD,CommonJS用于服务器,而后面两者用于浏览器。

ES6模块的设计思想是将两静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。

  1. ES6的模块自动采用严格模式。
  2. export命令:用于规定模块的对外接口。

    • 一个模块就是一个独立的文件。只有通过export输出,其他文件才可以使用。
      1
      export parma;
  3. import命令:加载模块(文件)

    1
    2
    3
    4
    import {parma} form 'module'
    <!--整体加载-->
    import * as obj form 'module'
    // as 命令:重命名引入的模板内容。
  4. export defaul 命令:本质就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。

  5. 模块之间可以继承。
  6. ES6 模块加载的实质:
    • ES6模块输出的值为值得引用。
    • ES6的运行机制是这样的,它遇到模块加载命令import时不会去执行模块,只会生成一个动态的只读引用,只有在真的需要使用的时候,再去模块取值。
  7. 循环加载:
    • 指的是a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。

总结

首先,要了解什么是javascript模块化,其次就是对模块解决方案的学习,这里并没有对这些方案(CommonJS,AMD,CMD等)进行深入,只是提供学习方向。
对于javascript模块化的使用和学习还有很长的路,这里整理的是一些学习方向吧。希望对读者有所帮助。