JS 诞生之初面向简单页面开发,没有模块的概念。后来页面逐渐复杂,人类构造到 IIFE 立即执行函数来模拟 模块;之前也有雅虎的实践,使用命名空间 作为模块名。最后衍生出 面向各种使用场景 的 JS 模块标准。例如:
面向浏览器的 AMD
面向 Nodejs 的 CommonJS
对于这种分裂状态 ES 标准也在尽力弥合。但是目前流行的实践是 UMD 模式。
1 AMD
AMD 是 requirejs 推广产出的规范,主要用于浏览器环境,通过 define 和 require 这两个定义模块、调用模块。
定义模块
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {exports.verb = function() {return beta.verb();
//Or:
return require("beta").verb();}
});
// 返回对象的匿名模块
define(["alpha"], function (alpha) {
return {verb: function(){return alpha.verb() + 2;
}
};
});
调用模块
require(['foo', 'bar'], function (foo, bar) {foo.doSomething();
});
define(function (require) {require(['a', 'b'], function (a, b) {//modules a and b are now available for use.});
});
2 commonJS
Node 应用由模块组成,采用 CommonJS 模块规范。
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像 Node.js 主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以 CommonJS 规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。
// a.js
// 相当于这里还有一行:var exports = module.exports; 代码
exports.a = 'Hello world'; // 相当于:module.exports.a = 'Hello world';
// b.js
var moduleA = require('./a.js');
console.log(moduleA.a); // 打印出 hello world
3 UMD
兼容 AMD 和 commonjs, 也兼容 全局变量定义的 通用的模块化规范
(function (root, factory) {if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'));
} else {// Browser globals (root is window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {function a(){}; // 私有方法,因为它没被返回 ( 见下面)
function b(){}; // 公共方法,因为被返回了
function c(){}; // 公共方法,因为被返回了
// 暴露公共方法
return {
b: b,
c: c
}
}));
4 ES6 Module
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export 和 import。
export 命令用于规定模块的对外接口,导出模块暴露的 api ;import 命令用于输入其他模块提供的功能, 引入其他模块。
/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {return a + b;};
export {basicNum, add};
/** 引用模块 **/
import {basicNum, add} from './math';
function test(ele) {ele.textContent = add(99 + basicNum);
}
5 ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
- ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的 import 有点像 Unix 系统的“符号连接”,原始值变了,import 加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
- 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import 时采用静态命令的形式。即在 import 时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。