图说JavaScript模块化的前生今世

楔子

JavaScript作为一款灵活性和可塑性较强的语言,由前端到后端显示了它及其旺盛的生命力。而JavaScript的模块化之路却比较坎坷和多元化,初学者可能会对这些概念及其关系搞得晕头转向,比如CommonJS、AMD、ES6、CMD、UMD等。本文将简要概述它们之间的关系和区别,为了理清它们之间的关系,我画了张图。
Javascript-Module-ES6-CommonJS-AMD-CMD-UMD

CommonJS

CommonJS规范出现的最早,它的目的很明确,就是解决JavaScript的模块开发规范问题,而且志向是远大的,计划打造浏览器和App的Web、桌面以及命令行生态系统(a goal of building up the JavaScript ecosystem for web servers, desktop and command line apps and in the browser)。NodeJs就是参考CommonJs规范实现的,这里重点突出两个关键词exports(导出模块)和require(加载模块),如

// ./blog.js
//导出模块
exports.blog={
say:function(name){
return 'hello, '+name;
}
}

// ./app.js
//加载模块
var blog=require('./blog').blog;
console.log(blog.say('zhaiqianfeng'));

执行输出

$ node app.js
hello, zhaiqianfeng

AMD & CMD

AMD

一切都那么完美,在NodeJs实现后,当人们开始热情的打算把这种实现也用于浏览器时,却发现并不适合。NodeJS应用加载的模块都是基于本地磁盘的,而浏览器却收到网络延迟的影响,而各个模块的延迟长短并不确定,这就给依赖造成了很大的麻烦,比如执行模块先于被依赖的模块下载下来了,那么是执行失败。于是AMD就出现了。

AMD(Asynchronous Module Definition),是参考CommonJS规范结合浏览器应用特点而制定的异步模块定义规范,典型的实现是RequireJS

AMD只定义了一个接口

define(id?, dependencies?, factory);

id是当前模块的标识,dependencies是当前模块依赖的模块数组,factory是把dependencies作为参数的模块真正实现,前两者都可以省略。
如定义AMD模块

// ./base.js
define(function(){
return {
say:function(name){
return 'hello,'+name;
}
};
});

AMD调用模块也是使用关键字require,但不同于CommonJS,而是和AMD定义相似

require([module], callback);

因此调用base模块的代码如

// ./app.js
require(['base'],function(base){
var words=base.say('zhaiqianfeng');
document.write(words);
});

先引用AMD的实现RequireJS,html代码如下

<script src="require.js"></script>
<script src="app.js"></script>

CMD

此时简单提下CMD(Common Module Definition),你可以简单理解为是对AMD的优化版:AMD是依赖前置,提前加载依赖;而CMD依赖后置,使用时才加载。写法和性能都有所改善,但结果其实并不理想。而且现在RequireJS也有延迟加载功能了。CMD的典型实现是SeaJS。

UMD

UMD(Universal Module Definition)的出现是由于:CommonJS侧重服务器,而AMD侧重于浏览器,两者的模块不能共享。UMD的思想很简单,判断是AMD则使用AMD方式,是CommonJS则使用CommonJS方式,都不是则将模块公开给全局(window或global)。

(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD方式
define(['b'], function (b) {
return (root.returnExportsGlobal = factory(b));
});
} else if (typeof module === 'object' && module.exports) {
// Node/CommonJS方式
module.exports = factory(require('b'));
} else {
// 公开暴露给全局
root.returnExportsGlobal = factory(root.b);
}
}(this, function (b) {
return {};
}));

ES6/ES2015

此时貌似一切都已经很完美了,但话说早在2013年12月ECMAScript6草案就已经发布了,其中就涉及到模块化的内容,ECMAScript6正式发布于2015年,因此简称为ES6或ES2015。此时NodeJS又实现了ES6,但只是部分实现,到目前import等并不支持,因此如果在NodeJS中使用ES6目前最好的方式是使用Babel这样的工具来转换。
Babel
Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,号称是下一代JavaScript编译器。

整理知识点,方便自己的同时,如果能够帮助别人那就是最大安慰。本文一次收尾,不在对Babel展开描述,有兴趣的可参考Babel官方,详细看过这篇博文的朋友都得赶兴趣,它是个时髦货。

翟前锋 wechat
欢迎订阅我的微信公众号:zhaiqianfeng!