在 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