webpackシリーズ1-循環依存の分析と処理の概要

一緒に書く習慣をつけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して2日目

循環依存とは何ですか?

一般に、開発はあまり注意を払わず、循環依存に遭遇することもありませんが、プロジェクトの複雑さが増すにつれて、特に複雑な依存関係を持つ大規模なプロジェクトでは、循環依存が発生しやすく、予期しない問題が発生する可能性があります発生するので、コーディングプロセスの将来では、この側面の認識を強化する必要があります。

いわゆる循環依存関係とは、簡単に言うと、a.jsがb.jsに依存し、bのスクリプト実行もa.jsに依存することを意味します。

デモで循環依存とは何かを説明します。

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;

复制代码

コンパイルされたコードimage.png webpackを実行する ためのエントリファイル はmain.jsであり、それらの参照は次のとおりです。main.js –> a.js –>  b.js –> a.js –> b.js

問題は、  a.js が循環的に参照されることです。b.jsに インポートされた a.jsの参照値 は未定義ですが、最後の 1つの 結果を取得する必要があります。

循環依存が発生する理由

上記のデモ全体は、webpackによって次のコードにコンパイルされます

image.png

これは即時実行関数です。ブラウザがjsをロードすると、実行がトリガーされます。即時関数のパラメータはオブジェクト、キーは依存ファイルパス、値はファイルのコンパイル済みコンテンツです。

そして、エントリファイルmain.jsをパラメータとして渡し、__ webpack_require__メソッドを実行して、プロジェクトコード全体の実行を開始します。

image.png

この関数では、次のことが行われます。

1、判断全局 installedModules 是否有对应的 moduleId 值,有的话就导出其引用

2、如果没有,就执行 modules[moduleId]  方法,最后返回该模块 module 的引用

也就是说它会执行对应依赖里面的代码,执行后将该依赖再保存到installedModules对象里,下次就不要再执行了

image.png

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 ,这就是问题的出现原因。

image.png

怎么解决?

总结3种处理循环依赖的方法(也欢迎各位小伙伴补充)

  • 单独export一个方法 ⭐️⭐️⭐️
    • 这种适合一个巨无霸对象,某个属性的方法被别的文件引用,别的文件引用的时候是只能全量引入bigObject,然后这样使用bigObject.a(),这种情况下我们可以将方法a,单独抽离出来并export出去,减少循环依赖并使产出最小。
  • 将功能尽可能小的封装在一起,需要某个功能直接按路径引用⭐️⭐️
    • 这种也是适合一个巨无霸对象,但是某个属性的功能是集成了其他对象的某个方法,别的文件引用的时候也只能全量引入bigObject,这种情况下,我们可以将这种功能单独抽离出一个js,然后用路径的方式去引用import a from bigObject/a.js,这样也能减少循环依赖的情况,可能使产出最小。
  • 最小复制原则 ⭐️
    • 有时候循环依赖的环可能是比较长的,我在重构的时候遇见的最长的依赖环是10个文件组成一个闭环,但是如果环中只是因为大家共同引用了一个纯函数工具方法,那我们就干脆霸道的将这个方法复制一份放到某个目录下就好了,推荐指数一颗星。

未雨绸缪:介绍一个插件

circular-dependency-plugin

能够在编译过程中检测项目代码中是否存在循环依赖,如果存在循环依赖就会再控制台输出警告,建议再开发环境下可以配置一个,能够未雨绸缪,毕竟有时候当循环依赖的闭环太长的时候,肉眼是很难区分出来的。

image.png

欢迎小伙伴们补充

以上3种方法是实践中我用来处理循环依赖的,欢迎有其他方法的小伙伴补充哈

おすすめ

転載: juejin.im/post/7084600958524588040