CommonJS和ES6 Module分别如何处理循环依赖的问题?

前篇(CommonJS和ES6 Module的区别):https://juejin.cn/post/6959360326299025445

前言:一般来说工程中应该尽量避免循环依赖的产生,因为从软件设计的角度来说,单向的依赖关系更加清晰,而循环依赖则会带来一定的复杂度。

  • 但工程的复杂度上升到足够规模时,就容易出现隐藏的循环依赖关系。简单来说,A和B两个模块之间是否存在直接的循环依赖关系是很容易被发现的。但实际情况往往是A依赖于B,B依赖于C,C依赖于D,最后绕了一大圈,D又依赖于A。当中间模块太多时就很难发现A和B之间存在着隐式的循环依赖。

实际代码举例

目录结构:

  1. ├── main.js
  2. ├── bar.js
  3. ├── aaa.js

commonjs 循环依赖的处理情况(注释内有解析)

  1. // main.js
  2. const bar = require('./bar.js')
  3. console.log('当前是main.js内:', bar) // {}
  4. module.exports = '在main.js内'
  5. // bar.js
  6. const main = require('./main.js')
  7. console.log('当前是bar.js内:', main)
  8. module.exports = 'bar.js内'
  9. // 执行 node ./main.js , 输出:
  10. 当前是bar.js内: {} // 解析:执行到bar.js内时,main.js还没有执行完,就没有东西导出,会默认导出空对象
  11. 当前是main.js内: bar.js

es6 module 循环依赖的处理情况(注释内有解析)

  1. // main.js
  2. import bar from './bar.js'
  3. console.log('当前是main.js内:', bar)
  4. export default '在main.js内'
  5. // bar.js
  6. import main from './main.js'
  7. console.log('当前是bar.js内:', main)
  8. export default 'bar.js内'
  9. // 执行 webpack-dev-server xx 启服务去执行 入口是main.js , 输出:
  10. 当前是bar.js内: undefined // 解析:编译到bar.js内时,main.js还没有编译完,就没有东西编译映射到main这个变量上去,就是undefined
  11. 当前是main.js内: bar.js

解决方法

只能用es6 module,因为只有他的值的动态映射的,可以得到变更的状态

核心思路

  1. es6 module导出的值是动态映射的
  2. 用function包裹,让加载先不立即执行
  3. 用一个变量,来解决重复调用的问题(闭包)
  1. // main.js (为了打包方便,此处main.js只作为入口,aaa和bar循环依赖)
  2. import aaa from './aaa.js'
  3. aaa('main')
  4. // aaa.js
  5. import bar from './bar.js'
  6. function aaa (name) {
  7. console.log('当前是aaa.js内:', name)
  8. bar('aaa')
  9. }
  10. export default aaa
  11. // bar.js
  12. import aaa from './aaa.js'
  13. let time = false
  14. function bar (name) {
  15. if (!time) {
  16. time = true
  17. console.log('当前是bar.js内:', name)
  18. aaa('bar')
  19. }
  20. }
  21. export default bar
  22. // 执行 webpack-dev-server xx 启服务去执行 入口是main.js , 输出:
  23. 当前是aaa.js内: main
  24. 当前是bar.js内: aaa
  25. 当前是aaa.js内: bar

参考《Webpack实战:入门、进阶与调优》(居玉皓)