Commonjs与Es6Module区别1: (最本质的)
前者在模块的处理上是动态的后者则是静态的
- 动态的含义: 模块间的依赖关系建立发生在代码
运行阶段 - 静态的含义: 模块间的依赖关系建立发生在代码
编译阶段
来几个列子论证一下
- Commonjs例子
//A.js
const name = require('./B.js').name
//B.js
module.exports = {name: 'commonjs'}
当模块A加载模块B时,会执行模块B的代码 require函数返回值 = module.exports 返回对象, 甚至我们可以通过条件判断返回的模块,因此在commonjs模块没执行前 我们是无法明确依赖关系的
- Es6Moudle例子
//A.js
import {name} from './B.js'
//B.js
export const name = 'Es6Modulejs'
es6的模块导入是声明式 不支持表达式。并且导入、导出的语句必须在模块顶层作用域。
-Es6Module优势:
死代码检测和排除。可以通过静态分析工具检测模块有没有被调用,减少包的体积
模块变量类型检查。javascript是动态类型语言,无法在代码执行前检测类型错误(对字符串变量进行函数调用)。Es6Module是静态模块结构有助于确保模块间值和接口类型的正确性
编译器优化。 Commonjs动态模块 本质上导出的是一个对象 而Es6module直接导出变量,减少了层级引用。
Commonjs与Es6Module区别2: 值拷贝与动态映射
Commonjs: 获取的是值拷贝 Es6Module:获取的是值的动态映射,并且这个值是只读的。
来几个列子论证一下
- Commonjs:
//B.js
var count = 0
moudule.exports = {
count: count
add: function (a, b) {
count+=1
return a + b
}
}
//A.js
var count = require('./B.js').count
var add = require('./B.js').add
add(2,3)
console.log(count) //0(B.js中被拷贝的值没有被修改)
count+=1
console.log(count) //1(拷贝的值可以修改)
A.js中的count是值拷贝,虽然add改变了B.js中的count,但是并不会对A.js中的造成影响
- Es6Module
//B.js
let count = 0
const add = (a,b) => {
count+=1
return a+b
}
export {count,add}
//A.js
import {count,add} from './B.js'
add(2,3)
console.log(count) //1 (B.js中count的映射)
count+=1 //抛异常 ‘count’ is read-only
A.js中的count是对B.js中count的动态映射,可以理解为B.js值的实时反应,但是不可以做出修改,打个比方:A是一面镜子,可以观察镜子中值的变化,但是不可以操控镜子中的镜像。
Commonjs与Es6Module区别3: 延伸出的循环依赖
如何处理循环依赖是开发者必须面对的问题!我会对Commonjs和Es6Module中的循环依赖做对比
- Commontjs:
//A.js
require('./B.js')
//B.js
const bar = require('./C.js')
console.log('B.js',bar)
module.exports = 'this is moduleB'
//C.js
const foo = require('./B.js')
console.log('C.js',foo)
module.exports = 'this is moduleC'
输出:C.js {}; B.js this is moduleC
why?
- A.js 引用了 B.js 进入B.js 执行代码
- B.js 第一行引用了 C.js 进入C.js 不再继续执行B.js剩下的代码
- C.js 第一行引用了 B.js(产生循环依赖) 不会再次进入B.js了 直接用B.js默认的导出( {} ) 继续执行C.js剩下的代码直到结束
- C.js 执行结束 将控制权交回B.js 执行结束
- 提问:
为什么第三步不会再次进入B.js 使程序陷入死循环呢?
Good 我们把这段代码放入webpack中打包后,bundle中有酱紫一段代码:
var __webpack_module_cache__ = {};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// Create a new module (and put it into the cache)
var module = __webpack_module_cache__[moduleId] = {
// no module.id needed
// no module.loaded needed
exports: {}
};
// Execute the module function
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// Return the exports of the module
return module.exports;
}
从bundle中可以看出 代码会判断 cachedModule 是否加载过 加载过就不会再次加载了。
- 结论:
酱紫我们就可以打断点 瞅瞅__webpack_module_cache__在执行结束未销毁的时候长什么样子了。可以看出B.js和C.js暴露出来的东东.
- Es6Moudle:
```
//A.js
import foo from './B.js'
//B.js
import bar from './C.js'
console.log('B.js',bar)
export default 'this is moduleB'
//C.js
import foo from './B.js'
console.log('C.js',foo)
export default 'this is moduleC'
```
输出C.js undefined; B.js:8 B.js this is moduleC
很遗憾C.js也没有输出,只不过和Commonjs不同的是这里获取到的是undefined,在bundle中可以看到exports["default"] = void 0;默认导出的是undefined.
-提问:
期望用循环依赖输出
C.js this is moduleB; B.js:8 B.js this is moduleC有没有什么办法呢?
//A.js
import foo from './B.js
foo('A.js this is moduleA')
//B.js
import bar from './C.js'
const foo = (invoker) => {
console.log(invoker)
bar('B.js this is moduleB')
}
export default foo
//C.js
import foo from './B.js'
let invoked = false
const bar = (invoker) => {
if(!invoked){
invoked = true
console.log(invoker )
foo('C.js this is moduleC')
}
}
export default bar
输出:A.js this is moduleA; B.js this is moduleB; C.js this is moduleC
- A.js作为入口,加载A.js, 第一行引入B.js, 将控制权交给B.js
- 加载B.js代码,第一行引入C.js, 讲控制权交给C.js
- C.js一直执行到完毕,完成了对bar函数的定义,此时C.js的foo函数还是undefined, 将控制权交还给B.js
- B.js 继续完成对foo函数的定义,将控制权交还A.js 执行foo('A.js this is moduleA') 此时foo 和 bar函数都已经初始化完成。
- 依次执行 foo('A.js this is moduleA') => bar('B.js this is moduleB') => foo('C.js this is moduleC') => bar('B.js this is moduleB') 但是此时invoked=true
-结论:
上述例子可以看到ES6的特性更好的支持了循环依赖,只需要开发者保证当值被使用时,已经导出了正确的值!
当然在现实开发过程中,我们是严禁产生循环依赖的,使用webpack中插件circular-dependency-plugin来检测是否有循环依赖。