文章首发公众号,欢迎关注
webpack系列1--循环依赖解析与处理方式总结
什么是循环依赖?
一般开发其实不太关注,也不太遇到循环依赖的情况,但是随着项目复杂度增加,尤其是依赖关系复杂的大项目,很容易出现循环依赖的情况,容易产生意想不到的问题,所以今后在编码过程中,要加强这方面的意识。
所谓循环依赖,简单来说,就是 a.js 依赖 b.js 而 b 的脚本执行,又依赖a.js。
用一个demo解释下什么是循环依赖:
webpack.config.js
module.exports = {
entry: './main.js',
}
mian.js
import a from './a';
console.log(a);
a.js
import b from './b';
export default b;
b.js
import a from './a';
console.log('a:', a);
export default 1;
执行编译后代码
webpack 的入口文件为 main.js,随后他们的引用方式为: main.js –> a.js –> b.js –> a.js –> b.js
问题就出在 a.js 被循环引用了,b.js 中导入的 a.js,其引用值为 undefined,虽然我们希望它得到最后的 1 结果。
为什么会发生循环依赖
上面的整个deme会被webpack编译成如下代码
这个是一个立即执行函数,当浏览器加载js后会被触发执行,立即函数的参数为一个对象,key为依赖的文件路径,value为该文件编译后的内容
并且将入口文件main.js 作为参数传入,执行webpack_require方法,开始整个项目代码的执行
在这个函数中,做了如下几个事
1、判断全局 installedModules 是否有对应的 moduleId 值,有的话就导出其引用
2、如果没有,就执行 modules[moduleId] 方法,最后返回该模块 module 的引用
也就是说它会执行对应依赖里面的代码,执行后将该依赖再保存到installedModules对象里,下次就不要再执行了
1、初次调用的时候,installedModules[moduleId] 为空,接着通过 modules[moduleId].call 执行对应的依赖代码。第一个被执行的就是入口文件main.js的代码
2、main.js 里导入了 a.js,则又会调用 webpack_require 方法
3、这里对 a.js 的调用并没有结束(返回值还没有拿到),由于 a.js 中又导入了 b.js,所以又会同上述步骤再执行 b.js 对应的函数。
4、但执行到b这里时就不同了( modules[b.js].call )
5、b.js 文件中导入了 a.js 文件,导致 a.js 作为参数又会进入到 webpack_require 方法。
6、因为先前 webpack_require(a.js) 被执行过了,所以在 modules[b.js].call 环节执行 webpack_require(a.js) 中,installedModules[moduleId] 判断为 true 了:
7、而最开始第一次导入 a.js 时,其返回值还没有拿到,所以此时为 true 后,返回值就为 undefined ,这就是问题的出现原因。
怎么解决?
总结3种处理循环依赖的方法(也欢迎各位小伙伴补充)
- 单独export一个方法 ⭐️⭐️⭐️
- 这种适合一个巨无霸对象,某个属性的方法被别的文件引用,别的文件引用的时候是只能全量引入
bigObject
,然后这样使用bigObject.a()
,这种情况下我们可以将方法a,单独抽离出来并export
出去,减少循环依赖并使产出最小。
- 这种适合一个巨无霸对象,某个属性的方法被别的文件引用,别的文件引用的时候是只能全量引入
- 将功能尽可能小的封装在一起,需要某个功能直接按路径引用⭐️⭐️
- 这种也是适合一个巨无霸对象,但是某个属性的功能是集成了其他对象的某个方法,别的文件引用的时候也只能全量引入
bigObject
,这种情况下,我们可以将这种功能单独抽离出一个js,然后用路径的方式去引用import a from bigObject/a.js
,这样也能减少循环依赖的情况,可能使产出最小。
- 这种也是适合一个巨无霸对象,但是某个属性的功能是集成了其他对象的某个方法,别的文件引用的时候也只能全量引入
- 最小复制原则 ⭐️
- 有时候循环依赖的环可能是比较长的,我在重构的时候遇见的最长的依赖环是10个文件组成一个闭环,但是如果环中只是因为大家共同引用了一个纯函数工具方法,那我们就干脆霸道的将这个方法复制一份放到某个目录下就好了,推荐指数一颗星。
未雨绸缪:介绍一个插件
能够在编译过程中检测项目代码中是否存在循环依赖,如果存在循环依赖就会再控制台输出警告,建议再开发环境下可以配置一个,能够未雨绸缪,毕竟有时候当循环依赖的闭环太长的时候,肉眼是很难区分出来的。
欢迎小伙伴们补充
以上3种方法是实践中我用来处理循环依赖的,欢迎有其他方法的小伙伴补充哈