CommonJs 和 ES6 module 的导入和导出

242 阅读3分钟

语法差别

CommonJS 模块使用require()module.exports,ES6 模块使用importexport

总结为一句话:.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载,.js文件的加载取决于package.json里面type字段的设置。

注意,ES6 模块与 CommonJS 模块尽量不要混用。require命令不能加载.mjs文件,会报错,只有import命令才可以加载.mjs文件。反过来,.mjs文件里面也不能使用require命令,必须使用import

ES6 模块与 CommonJS 模块的差异

它们有三个重大差异。

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

第二个差异产生的原因是 CommonJS 导出一个对象,对象需要在脚本运行完成时才生成. ES6 模块的对外接口是一种静态定义,在代码静态解析阶段就会完成.

循环加载

“循环加载”(circular dependency)指的是,a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。

CommonJS 模块加载原理

CommonJS 的一个模块就是一个文件. require命令第一次时,就执行完整个脚本, 并在内存中生成一个对象

{
  id: '...',
  exports: { ... },
  loaded: true,
  ...
}

这个对象的id属性是模块名,export属性是模块输出的全部接口,loaded属性是一个布尔值,表示该模块的脚本是否执行完毕。其他还有很多属性,这里都省略了。

以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。

CommonJS 模块的循环加载

a.js:

console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');

b.js:

console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');

main.js:

console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done);
$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done = true, b.done = true

image.png

整体思路,

  1. main.js引入a.jsa.js运行到第三行代码
  2. 进入到b.js
  3. 当运行到b.js的第三行代码,b.js知道是它是被a.js引入,所以b.js只会获取到a.js前两行,但仅只有exports.done = false生效。b.js往下执行,第五行导出done = true,最后结束。
  4. 返回到a.js的第三行,取到b.js 的 dong=true,
  5. 第五行导出done = true,最后输出。

ES6 模块的循环加载

ES6 处理“循环加载”与 CommonJS 有本质的不同。ES6 模块是动态引用,如果使用import从一个模块加载变量(即import foo from 'foo'),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。