我想先解释一下commonjs(cjs)的导出导入概念,结合他来理解es6 module(esm)更好理解。
在commonjs,大部分情况就是nodejs中,定义导出的时候其实就是exports对象,比如
javascript">module.exports = {a: 1, b: 2};
// 或者
module.exports.a = 1;
module.exports.b = 2;
以上,其实module.exports本身是一个Object,他作为commonjs规范下模块的默认(default)导出,这里可以理解为相当于ES6的export default
。
但值得注意的是,commonjs中仅仅支持这一种默认导出。不支持类似于ES6中:
javascript">export const a = 1;
export const b = 2;
以上是具名导出(named export), 也就是说如果你想使用’具名导入’的方式去导入一个commonjs的包,是会报错的。因为cjs根本就不支持。
javascript">// module1.cjs
module.exports = {a:1};
// module2.mjs
// const {a} = require('./module1') // 可以执行,因为前边是解构语法
import {a} from './module1.cjs' // 不可以执行,因为这个named import,不是解构。
上述代码执行报错如下:
SyntaxError: Named export 'a' not found. The requested module './module1.cjs' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from './module1.cjs';
const {a} = pkg;
这个报错非常的人性化,清楚的解释了你有一个语法错误,也就是import {a} from './module1.cjs'
这种导入会去被导入的包中寻找具名的导出,就是export const a = 1
这种导出。但cjs是不支持的,所以报错了。
到这里,我想简单总结下,在模块导入导出的过程中,对于cjs和esm,他们支持两组导出:
module.exports / export defalut
对应着require() / import anyName from './'
- 具名导入导出(named export)
export const a; export const b
对应着import {a, b} from './'
网上有文章说,最好不要使用export default
,究其原因是因为这个会引起歧义,比如以下这种场景:
javascript">//module1.js
export default {a: 1, b: 2};
// module2.js
import {a, b} from './a'
上述代码是错误的。因为module2中的语法不是解构,而是去寻找module1中的具名导出。而实际上module1中是没有的。所以会报出undefined。
实际上他是以下语法的简写
import {a as a, b as b} from './module.mjs'
而不是下面的简写
import {a: a, b: b} from './module.mjs'
如果你真的想去解构,应该这样写:
javascript">import module1 from './module.mjs'
const {a, b} = module1;
所以倒不是不让用export default,而是需要注意到潜在的问题。
最后,在模块导出的时候,你可以写一种兼容的写法,也就对于导入者来说,他怎么导入都不会错。
const api = {a, b}
// 有默认导出
export default api;
// 也有具名导出
export const {a, b} = api;