前端中循环依赖
什么是循环依赖: 两个以上模块之间互相引用,构成闭环依赖。
保持依赖引入的单向流通性!
示例:
不要觉得自己不会写出这样的代码,当项目庞大后一旦出现这样的问题将会造成无法排查的问题。
// 在a.js 引用 b.js 内容
import {b} from "./b.js"
export const a = () => {
omit...
}
// 在b.js 引用 a.js 内容
import {a} from "./a.js"
export const b = () => {
omit...
}
1. 为什么循环依赖会造成报错
循环依赖产生之后报错的原因,通常是由模块执行顺序造成的。
1.1 执行顺序
执行的顺序导致的模块先后加载时,出现未定义未初始化的报错。
-
ES6 modules
在ES6模块中,模块的加载顺序是由它们在代码中的出现顺序决定的。因此,如果两个模块相互引用,那么先出现的模块将先执行。 -
CommonJS
在CommonJS模块中,模块的加载顺序是由require函数的调用顺序决定的。因此,如果两个模块相互引用,那么先调用require函数的模块将先执行。
1.2 ES6 和 CommonJS 输出的值是和原始值是什么关系?(修改导出的值是否会影响原始值)
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。 -- 《ECMAScript 6 入门教程》
-
CommonJS 模块确实输出的是一个值的拷贝。当你使用 module.exports 导出一个值时,这个值是被复制的,而不是被引用的。这意味着,如果你导出一个对象,那么对这个对象的修改不会影响到其他模块。
-
ES6模块使用 export 关键字来导出值,并且默认情况下,这些值是被引用的,而不是被复制的。这意味着,如果你导出一个对象,那么对这个对象的修改会影响到其他模块。但是,有一种情况下,ES6模块会创建一个值的拷贝,那就是当你使用 export 导出一个函数或者类的时候。在这种情况下,函数或者类的代码会被复制,但是任何在函数或者类中引用的外部变量仍然会被引用。
2. 项目如何避免循环引用
本质上如何避免循环引用,就是用监测手段 循环引用 并及时切断。
2.1 集成在webpack等脚手架的循环依赖分析
webpack插件名字 circular-dependency-plugin
2.2 集成在EsLint的循环依赖分析
EsLint插件名字 eslint-plugin-import
,eslint 规则 import/no-cycle
2.3 dpdm 开源插件
插件名字 dpdm
使用 dpdm 定位 JavaScript/TypeScript 中的循环依赖
2.4 TS项目类型
使用 import type {} from './xxx'
模块的类型定义,而不导入实际的模块内容。而不加type则会类型和实际值都被导入