commonjs和ES Module
commonjs导出方法exports和module.exports的区别?
commonjs
实际导出的就是exports这个对象,可以把需要导出的变量、方法等绑定在exports
这个对象上导出,例如:
//a.js
exports.name = '阿明先森'
console.log(require('./a.js') );
打印:
{
"name": "amingxiansen"
}
小结:所以用exports
导出的都是对象,导出内容包含在对象里。
module.exports
和exports
具有相同的引用。module.exports
可以直接导出函数、变量等。例如:
module.exports = '阿明先森'
打印:
小结:当然想要导出对象也没问题:module.exports = {}
;相比exports
,更灵活也更容易理解,推荐使用module.exports
进行导出。
如果使用exports = {name: '阿明先森'}
导出对象会怎么样呢?
打印:
打印为空,原因是exports是导出函数传入的参数,exports = {}
相当于在函数里面重新声明了一个exports变量,和原来的参数脱离了关系,所以打印出来的是原来的参数,为空。
举个例子:
const myExports = {}
function Test(myExports) {
myExports = {name: 'amingxiansen'} //这里的myExports与参数脱离了关系,相当于在局部声明了let Exports
}
console.log(Test(myExports)) // {}
小结: 所以使用exports
导出,必须要把导出内容绑定在exports
上,作为它的一个属性导出。
module.exports ={
name:'amingxiansen'
}
exports.name = 'alien' // 此时 `exports.name` 是无效的
此时exports
会被module.exports
覆盖
小结: module.exports
优先级高于exports
,应该避免两种导出混合使用。
commonjs实现原理?
在编译过程中,commonjs
经过了收尾的包装,像下面这样:
(function(exports,require,module,__filename,__dirname){
const sayName = require('./hello.js')
module.exports = function say(){
return {
name:sayName(),
author:'我不是外星人'
}
}
})
在 Commonjs
规范模块中,会形成一个包装函数,我们写的代码将作为包装函数的执行上下文,使用的 require ,exports ,module
本质上是通过形参的方式传递到包装函数中的。
require加载原理?
每个文件都是一个module,module上除了有模块的基本信息之外,还会有个loaded代表模块是否已经加载过。二次加载会直接使用缓存中的数据。
以nodejs为例,整个系统运行之后,会用 Module 缓存每一个模块加载的信息。
加入缓存的顺序是:先加入缓存再执行
require源码:
// id 为路径标识符
function require(id) {
/* 查找 Module 上有没有已经加载的 js 对象*/
const cachedModule = Module._cache[id]
/* 如果已经加载了那么直接取走缓存的 exports 对象 */
if(cachedModule){
return cachedModule.exports
}
/* 创建当前模块的 module */
const module = { exports: {} ,loaded: false , ...}
/* 将 module 缓存到 Module 的缓存属性中,路径标识符作为 id */
Module._cache[id] = module
/* 加载文件 */
runInThisContext(wrapper('module.exports = "123"'))(module.exports, require, module, __filename, __dirname)
/* 加载完成 *//
module.loaded = true
/* 返回值 */
return module.exports
}
ES module
对模块化module的支持是从ES6以后开始的。ES module导入导出使用 import
和export
。可以导出函数、变量、对象等。
export default //默认导出
===> import temp from './a.js'
export {a, b, c}
===> import {a, b, c} from './a.js'
也可以混合导入导出。
es module的特性
- ES6 module 的引入和导出是静态的,import 会自动提升到代码的顶层 ,import , export 不能放在块级作用域或条件语句中。
import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。 - ES6 module 和 Common.js 一样,对于相同的 js 文件,会保存静态属性。
但是与 Common.js 不同的是 ,CommonJS 模块同步加载并执行模块文件,ES6 模块提前加载并执行模块文件,ES6 模块在预处理阶段分析模块依赖,在执行阶段执行模块,两个阶段都采用深度优先遍历,执行顺序是子 -> 父。 - es module import导入的是值的引用,但是read only的,直接修改会报错,可以通过方法修改;commonjs require导入的是属性的copy,可以更改导入属性。
总结
Commonjs 特性总结
- CommonJS 模块由 JS 运行时实现。
- CommonJs 是单个值导出,本质上导出的就是 exports 属性。
- CommonJS 是可以动态加载的,对每一个加载都存在缓存,可以有效的解决循环引用问题。
- CommonJS 模块同步加载并执行模块文件。
ES Module特性总结
- ES6 Module 静态的,不能放在块级作用域内,代码发生在编译时。
- ES6 Module 的值是动态绑定的,可以通过导出方法修改,可以直接访问修改结果。
- ES6 Module 可以导出多个属性和方法,可以单个导入导出,混合导入导出。
- ES6 模块提前加载并执行模块文件,
- ES6 Module 的特性可以很容易实现 Tree Shaking 和 Code Splitting。