Javascript 中有两种源文件,一种叫做脚本,一种叫做模块。脚本是可以由浏览器或 Node 环境引入执行的,而模块只能由 Javascript 代码用 import 引入。
模块中包含:import声明,export声明、语句。脚本中包含的是语句。所以脚本和模块的区别仅仅在于是否包含 import 和 export。浏览器中的脚本通过script 标签引入,如果要引入模块,需要给 script 标签加上 type="module"
。1
<script type ="module" src=""></script>
下面来区分各种模块规范。本文代码示例在此
CommonJS
主要用于服务端,同步加载模块,只有加载完成,才能执行后面的操作。
CommonJS 定义的模块分为: 模块标识(module)、模块定义(exports) 、模块引用(require)
暴露模块
CommonJS 暴露模块有两个,exports 和 module.exports1
2module.exports = value
exports.a = value
那这两个有什么区别呢?
module.exports 和 exports 都是引用类型,require 引入的内容是 module.exports 指向的内存块内容,require 是看不到 exports 对象的,exports 只是 module.exports 的引用,如果改变了 exports 的引用(也就是指向了一个新的内存地址),require 的还是 module.exports 的内容,所以最好一直用 module.exports。
引入模块
1 | require(xxx) |
第一次加载某个模块时,Node 会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的 module.exports 属性。
查看 CommonJS Demo
AMD: 异步模块定义,依赖前置。
CommonJS 规范主要用于服务端,那客户端的呢,而由于 CommonJS 同步的局限性,导致浏览器不能使用 CommonJS 规范,所以 AMD 规范就出来了,用于异步加载模块。AMD 是”Asynchronous Module Definition”的缩写,意思就是”异步模块定义”,
模块的加载不影响后续执行,依赖模块加载的语句,都会放在一个回调函数里,等到该模块加载完之后回调函数才运行。define(id?, dependencies?, factory)
第一个参数 id:字符串,表示模块的标识。
第二个参数 dependencies:数组,元素是依赖模块的id。
第三个参数 factory:回调函数,在依赖的模块加载成功后,会执行这个回调函数,参数是所有依赖模块的引用。
暴露模块
1 | //定义没有依赖的模块 |
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。RequireJS 通过 require 函数实现模块的加载。
引入模块
1 | require([module], callback); |
CMD: 通用模块定义,依赖就近。
CMD 是”Common Module Definition”的缩写,意思就是”通用模块定义”,CMD 是 Sea.js 在推广过程中对模块定义的规范化产出。
暴露模块
1 | //定义没有依赖的模块 |
引入模块
1 | // 使用模块 |
CMD与AMD区别
共同点:
- 都是为了实现浏览器 JavaScript 模块化开发。
- AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
- CMD 是 Sea.js 在推广过程中对模块定义的规范化产出。
区别: - 对于依赖的模块,AMD 是 提前执行,CMD 是延迟执行。不过从 RequireJS 2.0开始,也可以延迟执行。
- AMD 推崇依赖前置,CMD 推崇依赖就近。
ES6模块
在ES6之前,主要的模块规范是 CommonJS 和 AMD 规范,ES6模块设计思想是尽量静态化,使得编译时就确定模块的依赖关系(静态加载),便于做静态代码分析。同时,静态加载也限制了模块加载在文件中所有语句之前,并且不能使用表达式和变量这种运行时才能得到结果的语法结构。CommonJS 和 AMD 都只能在运行时确定。ES6 模块化的出现,旨在成为浏览器和服务器通用的模块解决方案,
export声明
模块导出变量的方式有两种,一种是独立使用 export 声明,一种是在声明型语句(let, const, var, function, class等)前加上 export。1
2
3
4// 独立使用export声明。
export {a, b, c};
// 声明型语句前加export。
export var a = 1;
export 还可以默认导出,使用 export default 表示默认导出一个变量值。使用 export default 来导出时,对应的 import 不需要大括号。1
2
3
4
5export default function add() { // 输出
// ...
}
import add from 'add'; // 输入
export default 输出一个 default 的变量,所以后面不能跟变量声明语句。1
2
3
4
5
6
7
8
9
10
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
要注意的是,export 导出的必须是一个接口,与模块内的变量是一一对应的关系。不能直接导出变量值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 报错
export 1;
// 报错
var m = 1;
export m;
// 正确
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};
import声明
1 | import x from "./a.js"; // 导入默认值 |
ES6模块与CommonJS模块区别
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口1
2
3
4
5
6
7// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
CommonJS 加载模块是加载整个对象,生成 _fs 对象,再从这三个对象上读取3个方法,因为只有运行时才能得到这个对象,所以称为“运行时加载”,而 ES6 模块1
2// ES6模块
import { stat, exists, readFile } from 'fs';
只会加载三个方法,别的不会加载,ES6 模块不是对象,对外接口是一种静态定义,所以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
CommonJS 模块输出一个对象,结果被缓存,在第一次运行的时候加载一次,之后从缓存里读。JS 引擎在对脚本进行静态分析的时候,遇到模块加载命令 import,会先生成一个只读引用,等脚本真正执行的时候,才会去模块里执行。所以 ES6 模块是动态引用,不会缓存值。