ES6 module

最后更新:
阅读次数:

CommonJS

CommonJS 是指服务器端模块的规范,该规范规定了如何在服务器端定义一个模块,和如何引入一个模块。 Node.js 的模块系统就采用了这个规范。

CommonJS 对模块的定义十分简单,主要分为模块引用、模块定义和模块标识 3 个部分。

  • 模块引用

通过 require() 方法,接受一个模块标识,以此引入一个模块的 API 到当前上下文。

// 引入模块
const math = require("math");

// 使用模块
math.add(1, 2); // 3
  • 模块定义

CommonJS 规范规定,一个单独的文件就是一个模块。

在模块中,存在一个 exports 对象,用于导出当前模块的方法或变量。并且在模块中还存在一个 module 对象,它代表模块自身,而 exports 是 module 的属性

// math.js
module.exports = {
add: function(a, b) {
return a + b;
}
};
  • 模块标识

模块标识就是上面传递给 require 方法的参数,它必须是符合小驼峰命名的字符串,并且可以不写文件后缀 .js

CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像 Node.js 主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以 CommonJS 规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD、CMD 解决方案。

AMD

AMD(Asynchronous Module Definition),中文名是异步模块定义,它是一个在浏览器端模块化开发的规范。

AMD 模块将被异步加载,模块加载不影响后面语句的运行。所有依赖某些模块的语句均放置在回调函数中。

AMD 规范只定义了一个函数 define,它是全局变量。

  • define(id?, dependencies?, factory);
    • id: 可选,定义模块名称。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字
    • dependencies: 可选,是一个数组,定义了当前模块依赖的其他模块。如果没有提供该参数,它应该默认为[“require”, “exports”, “module”]
    • factory: 定义了模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。
// 定义一个没有依赖的模块
define('addonce',function(require,exports,module){
exports.add1 = function(a,b){
return a+b;
}
});


// 定义一个有依赖的模块
define('addtwice', [ 'require', 'exports', 'addonce' ], function( require, exports, addonce ){
export.add2 = function(){
return addonce.add1();
// 或者 return require('addonce').add1();
}
});

CMD

CMD(Common Module Definition),它是 SeaJS 在推广过程中对模块定义的规范化产出的。

这个规范也是一个浏览器端模块化的规范,但是它与 AMD 略有区别,有兴趣的点这里:AMD 和 CMD 的区别有哪些?

ES6 模块

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

  • ES6 模块输出的是值的引用,而 CommonJS 模块输出的是一个值的拷贝

export 语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新。

  • ES6 模块是编译时加载,而 CommonJS 模块是运行时加载

由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)类型检验(type system)这些只能靠静态分析实现的功能。

// module.js
export let foo = "1111111";

setTimeout(() => (foo = "2222222"), 2000);
// app.js
import { foo } from "./module.js";

setInterval(() => {
// 循环打印 foo 的值
console.log(foo);
}, 500);
  • ES6 模块与 Nodejs 模块不同,ES6 模块不会把模块当一个对象来处理,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入

不管在 import 语句中把一个模块引入多少次,该模块只会执行一次

// namedexport
export const PI = 3.14;

// import
import { PI } from "./math";
import { PI as MY_PI } from "./math";
// named export
let PI = 3.14;

export { PI };
export { PI as MY_PI };

// import
import { PI, MY_PI } from "./math";
import * as Obj from "./math"; // 引入模块的所有接口
  • import 导入的变量、函数、类是只读的,不能直接修改,但可以在被导出的模块中进行修改
// export
export let name = "percy";
export function setName(newName) {
name = newName;
}

// import
import { name, setName } from "./module.js";

console.log(name); // percy
setName("GG");
console.log(name); // GG

name = "mike"; // 报错
  • import 和 export 命令只能在模块的顶层,不能在代码块之中(比如,在 if 代码块之中,或在函数之中),否则会报错
// namedexport
if (true) {
export const PI = 3.14; // 报错
}

// import
import { PI } from "./math";
  • 有两种不同的 export 类型:named exportdefault export,并且每个模块只能有一个 export default,多了会报错
export default 3.14;
// export default function() {};
// export default class {};

// import
import any_PI from "./math";
  • 导入和导出结合使用(常用于在一个文件中导出多个模块)
// 复杂
import {sum} from './module1.js';
import {setName} from './module2.js';

export {sum, setName};


// 简化
export {sum} from './module1.js';
export {setName} from './module2.js';

至于 Nodejs 对 ES6 模块的进展,请看这篇文章。(Node.js 支持 ES6 模块的进展

参考资料