想了半天标题,终于决定还是它了。主要就是对比两个规范下的模块系统。
对于ES6模块系统,截止目前还没有环境原生支持,大多通过babel转换来实现。node环境的模块系统本身就是CommonJS的一个实现,浏览器环境通过webpack进行构建也可以支持大部分CommonJS,所以这个话题多数场景与宿主环境没有直接关系。
接下来开始VS:
导入模块
CommonJS:
ES6:
看起来似乎ES6更加『正统』,还霸占了两个关键字呢(/得意).
导出模块
CommonJS:
这是较常见的,还可以这样:
甚至这样:
ES6:
别的模块可以按需取出需要的东西,比如我只想要anyData
:
|
|
比如我只想要a
:
|
|
看到这里,似乎还是ES6看起来更加优雅。是的,这是一般的,多数的场景。而且,经过babel的转译,ES6的导入还兼容CommonJS的导出。
ES6兼容CommonJS
CommonJS导出 a.js
ES6导入:
观察一下babel转译后的代码:
发现导出模块时,babel会给当前模块对象添加__esModule: true
这个键值对,表示是ES6模块。为ES6的默认导出,指定default
这个key。当导入模块时,会做一个预处理,判断当前模块的__esModule
是否为true
,如果为true
,则直接返回,否则返回包装了一层的{ default: obj }
。如此就兼容了CommonJS模块,感觉棒棒哒,但是如果反过来呢?
ES6导出 b.js:
|
|
CommonJS模块:
我们拿到的将会是:
|
|
我们必须通过default
这个key去访问默认导出, 虽然可行,但就是觉得不舒服!也许你可以说,我们可以用单向导入啊,只允许ES6模块导入CommonJS模块,不允许反过来,这样就不会出现default这种『异类』了。但是你真的能保证,永远是单向吗?难保哪个CommonJS模块有一天就『逆反』,需要引入ES6模块了。node服务端有些配置模块,没有被babel转译,只能使用CommonJS,有时候需要加载被babel转译的ES6模块,这时候真的就要疯了。如果你依然觉得ES6模块很完美的话,请接着往下看。
CommonJS杀手锏–动态加载
ES6模块是不支持动态加载的,即它的导入模块不能是表达式,只能是字面量。而CommonJS是可以的,而且node环境已经完美实现了。我想这应该是node6.x到目前为止还未原生实现ES6模块系统的原因之一吧.
举个栗子: 我想导入test目录下的所有模块。ES6无法实现,CommonJS可以这样写:
但是,需要强调的是,动态加载在node环境下表现良好。在浏览器端,源码是经过构建工具(典型如webpack)构建过的,构建工具只能做静态分析, 为了保证模块不丢失,在运行时不会找不到目标模块,构建工具会把匹配目标模块表达式的所有模块全部打包,极端情况下,会造成很大浪费。因为浏览器需要从服务器下载代码,打包多余的代码对网页性能是一种隐患。
这一点,就是开头所说的『所以这个话题多数场景与宿主环境没有直接关系。』
——-update—–
用webpack做了个实验验证:
假设目录zzz
下有a.js
, b.js
, c.js
三个模块,模块内容分别为:
|
|
|
|
|
|
我在主模块下动态引入a.js
:
|
|
结果是对的,打印了aaaaa
但是观察一下打包出来的bundle,里面也附带了b.js
和c.js
的内容!!! 也就是我上面说的隐患!!!
注意: webpack的require.ensure不支持动态模块写法
总结
虽说ES6模块在功能上比CommonJS稍逊,但ES6模块毕竟是未来的标准。网上据说ES6有动态加载的API,但是我没有找到,似乎还在TODO列表中。鉴于浏览器使用动态加载有一定隐患,我们尽量避免使用动态加载。另外,前端代码可以通过构建工具全部转译(至少能够保证单向依赖),因此前端代码使用ES6模块并没有什么问题。而在node环境,对动态加载的需求更高,那么还是使用CommonJS吧。
ps: 观察了一些知名node框架/库,它们也还是使用CommonJS。