RequireJS用得很多了, 但是CommonJS和ES6的模块还没有系统地学习过, 经常看到各式各样的require
和import
, 有些晕, 特此总结.
这里对比的是:
- 来自CommonJS的
require
- 来自ESM (ECMAScript Modules)的
import
语法
CommonJS require
// dep.js
dep = {
foo: function(){},
bar: 22
}
module.exports = dep;
// app.js
var dep = require("dep");
console.log(dep.bar);
dep.foo();
ESM import
// dep.js
export foo function(){};
export const bar = 22;
// app.js
import {foo, bar} from "dep";
console.log(bar);
foo();
依赖解析
CommonJS require的依赖解析
Node.js使用的是CommonJS模式. 它将require
的代码用函数包裹起来, 如下:
function (exports, require, module, __filename, __dirname) {
const m = 1;
module.exports.m = m;
}
可以看到exports
, require
, module
都是局部变量, 你代码中的require()
和module.exports
其实都是使用了这些局部变量.
这里有个小问题, 那exports
和module.exports
有什么区别?
参考我的另一篇文章CommonJS中exports和module.exports的区别
require
加载分为5步:
- 解析 (Resolution): node内部解析文件的路径
- 加载 (Loading): 从对应路径加载文件
- 包裹 (Wrapping): 使用上面的代码包裹拉取来的文件
- 运算 (Evaluation): 由VM运算文件中的内容
- 缓存 (Caching)
因此, 在Evaluation之前, node是无法知道一个CommonJS的Module里面导出了那些符号的, 这是其与ESM最大的不同.
ESM import的依赖解析
ESM是语法层面(Lexical)上的实现, 在代码被Evaluate之前就可以知道那些变量被导出了. 当ESM模块被词法解析时, 交给VM运算之前, 它会内部构建一个Module Record
结构, 其中记录了一个导出变量的列表. 对于import {f} from "foo"
, 它自动建立了一个本地局部的f
和foo
模块内部的f
之间的映射.
因此, 导入导出变量中的不匹配, 在代码运行之前就会被发现并报错.
总结
require | import | |
---|---|---|
来源 | CommonJS | ESM (ECMAScript Modules) |
实现手段 | 函数包裹 | 语法层面 |
导出符号时间 | 运行时 | 解析时 |
报错时间 | 运行时 | 解析时 |