在 JavaScript 模块化编程的模块引入上,主要有两种方式,如下
CommonJS模块标准ES6 moduel特性
但是我们需要注意的是,CommonJS 模块规范和 ES6 模块规范完全是两种不同的概念,下面我们就来看看他们之间的区别
CommonJS 模块规范
Node.js 应用由模块组成,它采用了 CommonJS 模块规范,根据这个规范,每个文件就是一个模块,有自己的作用域,在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见的,CommonJS 规范规定,每个模块内部,module 变量代表当前模块,这个变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口,加载某个模块,其实是加载该模块的 module.exports 属性,如下
1 | var x = 5 |
上面代码通过 module.exports 输出变量 x 和函数 addX,而我们使用 require 方法就可以来加载该模块
1 | var example = require('./example.js') |
exports 与 module.exports
我们在一般开发过程当中导出模块的方式有两种方式,第一种就是直接对 module.exports 赋值,这个也是使用较多的
1 | module.exports = { |
或者也可以直接使用 exports,即直接在该对象上添加方法,表示对外输出的接口,如同在 module.exports 上添加一样
1 | exports.addX = addX |
但是针对上面的第一种用法,你不可以直接对 exports 赋值,就像下面这样
1 | // 错误的使用方式 |
代码虽然可以执行,但是模块并没有输出任何变量,这里就需要注意了,是不能直接将 exports 变量指向一个值的,因为这样操作就等于切断了 exports 与 module.exports 之间的联系了
那么这里就存在一个问题了,exports 和 module.exports 到底有什么区别和联系呢?这里就要提起 require() 这个方法了,在官方 API 当中,有一个经典的例子就是把 require 方法简化成了下面这个函数
1 | function require() { |
通过观察传入进去的参数,我们可以看到 exports = module.exports,所以说,一开始这两个东西是指向同一个对象实例的(实际上是同一个变量,并且初始化为空对象 { } ),如下图所示

如果直接添加一个方法,比如 exports.fn = func,那么由于 exports 指向 module.exports,由于复制引用的关系,相当于 module.exports 也添加了同样的方法,类似下图

1 | // 在 exports 上添加了一个方法,module.exports 也添加了同样的方法,因为它们指向了同样的对象实例 |
如果给 exports 直接赋值,比如这里 exports = some_func,那么 exports 的指向就变了,但最后导出的是 module,因此,exports 指向的方法或者今后在 exports 上添加的方法都不会影响到 module.exports,因此不会被导出

引用官方 API 的一句话
As a guideline, if the relationship between exports and module.exports seems like magic to you, ignore exports and only use module.exports.
所以还是建议尽量使用 module.exports,但是在有的时候,比如我们要输出的是一个函数或者数组,那么,只能给 module.exports 赋值
1 | module.exports = function () { return 'foo' } |
给 exports 赋值是无效的,因为赋值后,module.exports 仍然是空对象 {} (因为最后导出的是 module),我们可以简单的总结一下
- 如果要输出一个键值对象
{},可以利用exports这个已存在的空对象{},并继续在上面添加新的键值 - 如果要输出一个函数或数组,必须直接对
module.exports对象赋值
所以我们可以得出结论,直接对 module.exports 赋值,可以应对任何情况
1 | module.exports = { |
或者
1 | module.exports = function () { return 'foo' } |
总之,尽量使用 module.exports 来输出模块变量
ES6 模块规范
在看完了 CommonJS 模块规范之后,我们再来看看 ES6 当中的模块规范,不同于 CommonJS,ES6 使用 export 和 import 来导出、导入模块,在 ES6 模块当中比较容易混淆的也就是 export 和 export default 以及它们对应的引入方式,我们先来看看它们的区别,如下
export与export default均可用于导出常量、函数、文件、模块等- 在一个文件或模块中
export和import可以有多个,但是export default仅有一个 - 通过
export方式导出,在导入时要加{ },export default则不需要 export能直接导出变量表达式,export default不行
我们先来看看如何进行导出,先来看看通常的 export 导出方式
1 | // 导出变量 |
export default 导出方式如下,这里需要注意,不能写成 export defult const m = 100 这样的格式
1 | const m = 100 |
另外还有一种使用较少的导出方式
1 | function say() { |
我们假设上方代码是写在 test.js 当中的,引入方式如下
1 | import { say } from './test' // 导出了 export 方法 |
区别
最后我们再来简单的看看它们两者之间的区别,如下所说
CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用CommonJS模块是运行时加载,ES6模块是编译时输出接口CommonJS是单个值导出,ES6 Module可以导出多个CommonJS是动态语法可以写在判断里,ES6 Module静态语法只能写在顶层CommonJS的this是当前模块,ES6 Module的this是undefined