当前位置: 首页 > news >正文

前端模块循环依赖问题

模块循环依赖问题

在项目比较小的时候可能不怎么会遇到这个问题,但项目一旦有一定的体量后就可能会遇到了。
我之前做项目时就遇到这个问题,也是总结一篇文章。

比如这种类型的报错
在这里插入图片描述

commonjs存在的问题

先讲一下commonjs存在的问题。
CommonJS模块采用深度优先遍历,并且是加载时执行,即脚本代码在require时就全部执行。一旦出现某个模块被“循环加载”,就只输出已经执行的部分,没有执行的部分不会输出。
举例子

// a.js
require("./b.js");
exports.a = function () {};// b.js
const { a } = require("./a");
a();// index.js
require("./a.js");

执行index.js
结果:报错a is not function
执行流程
1 导入a.js

require("a.js")
// 此时moduleCache
moduleCache = {moduleA : {}
}

2 执行a.js为moduleA添加属性,发现第一行导入b.js,模块a还没执行完,执行b.js

require("./b.js");
// 此时moduleCache
moduleCache = {moduleA : {},moduleB : {}
}

3 执行b.js,发现导入a.js,此时moduleCache有moduleA,不会重复执行模块a的代码,会直接用moduleCache中模块a已经导出的内容。

const { a } = require("./a");
等价于
const {a} = moduleCache.moduleA

因为此时模块a的内容还未完全执行完,所以解构的变量a是undefined,还不是function,所以报错。

webpack打包结果分析

// a.js
import "./b.js";
export const A = () => {};// b.js
import { A } from "./a.js";
A();// index.js
import "./a";

webpack打包结果

(() => {"use strict";var __webpack_modules__ = {"./src/a.js": (__unused_webpack_module,__webpack_exports__,__webpack_require__) => {__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__, {A: () => A,});var _b_js__WEBPACK_IMPORTED_MODULE_0__ =__webpack_require__("./src/b.js");var A = function A() {};},"./src/b.js": (__unused_webpack_module,__webpack_exports__,__webpack_require__) => {__webpack_require__.r(__webpack_exports__);var _a_js__WEBPACK_IMPORTED_MODULE_0__ =__webpack_require__("./src/a.js");(0, _a_js__WEBPACK_IMPORTED_MODULE_0__.A)();},};var __webpack_module_cache__ = {};function __webpack_require__(moduleId) {var cachedModule = __webpack_module_cache__[moduleId];if (cachedModule !== undefined) {return cachedModule.exports;}var module = (__webpack_module_cache__[moduleId] = {exports: {},});__webpack_modules__[moduleId](module, module.exports, __webpack_require__);return module.exports;}(() => {__webpack_require__.d = (exports, definition) => {for (var key in definition) {if (__webpack_require__.o(definition, key) &&!__webpack_require__.o(exports, key)) {Object.defineProperty(exports, key, {enumerable: true,get: definition[key],});}}};})();(() => {__webpack_require__.o = (obj, prop) =>Object.prototype.hasOwnProperty.call(obj, prop);})();(() => {__webpack_require__.r = (exports) => {if (typeof Symbol !== "undefined" && Symbol.toStringTag) {Object.defineProperty(exports, Symbol.toStringTag, {value: "Module",});}Object.defineProperty(exports, "__esModule", { value: true });};})();var __webpack_exports__ = {};__webpack_require__.r(__webpack_exports__);var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/a.js");
})();

每个模块的代码会被放到一个对象

var __webpack_modules__ = {[moduleId] : 模块代码
}
var __webpack_modules__ = {"./src/a.js": (__unused_webpack_module,__webpack_exports__,__webpack_require__) => {__webpack_require__.r(__webpack_exports__); // 标记模块为ES模块__webpack_require__.d(__webpack_exports__, {A: () => A, // getter});var _b_js__WEBPACK_IMPORTED_MODULE_0__ =__webpack_require__("./src/b.js");var A = function A() {};},"./src/b.js": (__unused_webpack_module,__webpack_exports__,__webpack_require__) => {__webpack_require__.r(__webpack_exports__);var _a_js__WEBPACK_IMPORTED_MODULE_0__ =__webpack_require__("./src/a.js");(0, _a_js__WEBPACK_IMPORTED_MODULE_0__.A)();},};

webpack自定义require导入函数

function __webpack_require__(moduleId) {var cachedModule = __webpack_module_cache__[moduleId];if (cachedModule !== undefined) {return cachedModule.exports;}var module = (__webpack_module_cache__[moduleId] = {exports: {},});__webpack_modules__[moduleId](module, module.exports, __webpack_require__);return module.exports;
}

跟commonjs规范类似

  1. 查看缓存是否有模块导出结果,如果模块执行过了,返回模块导出结果
  2. 在执行模块代码之前,先创建模块导出对象module.exports
  3. 将模块导出对象传入执行模块代码
__webpack_require__.d // 定义模块导出属性
__webpack_require__.o // 检查模块导出对象是否具有某个属性
__webpack_require__.r // 标记模块为ES模块

模块代码执行前会先进行

  1. 将模块导出对象标记ES模块
  2. 如果模块有导出内容,会将这些内容定义到模块导出对象

代码执行流程
模块A执行

__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {A: () => A, // 定义getter
});
var _b_js__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__("./src/b.js"); // 执行到这里 会暂停a模块代码执行,执行b模块var A = function A() {};

moduleA 定义了一个A属性,A属性是一个存取器属性,有getter,getter就是返回真正导出的A。
执行b模块时,()=>A,这里返回的A还是undefined。
执行b模块

__webpack_require__.r(__webpack_exports__);
var _a_js__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__("./src/a.js");(0, _a_js__WEBPACK_IMPORTED_MODULE_0__.A)();

跟Commonjs的问题一样,模块A还没有执行完,A还没有赋值,所以A这里是undefined,不能作为函数调用。
这里和commonjs还是有些区别
打包结果中模块代码执行前会去先定义导出属性,为属性设置一个getter,因此在代码模块执行前这些导出属性就已经在导出对象中有getter。
这里因为配置babel,打包结果会把const转成var,所以变量声明提升了,如果是const就会变成变量在声明前使用。

结论

项目会形成循环依赖实际开发中很难避免,有可能引入了某个模块就会导致形成依赖链路。

形成循环依赖链路并不一定会报错,但是在执行到对应模块时,之前模块因为导入其他包,模块代码还没完全执行完,内容还没完全导出,就有可能导致报错。
其实导致报错还好,因为可以提前在本地就感知到处理,但是如果你只是定义了一个变量,那么这个变量可能是在你还没有赋值的时候,就引用了,所以其实模块导出的变量并不是一定可信的。

其实在遇到函数调用报错时可以通过把一些函数表达式改成函数声明就好,因为打包结果模块的执行其实是执行一个函数,在执行前会有函数声明提升,但尽量不要用这种规范来处理,因为很可能会遇到更多坑。
其实有模块循环依赖后还报错,本身就是这条依赖链路有问题,应该找到不合理的地方解决,而不是去规避。用函数声明解决一些问题,反倒会留下一些坑,可能某些环境的值原本因为循环依赖导致引用时是undefined,但是碰巧你用函数声明避免了一些报错,导致埋了一个坑。

有一些工具可以分析项目中的循环链路,eslint也有相应的配置。
至于如何找到循环依赖的不合理地方就凭经验吧,这里就不展开了,毕竟是个人观点。

相关文章:

前端模块循环依赖问题

模块循环依赖问题 在项目比较小的时候可能不怎么会遇到这个问题,但项目一旦有一定的体量后就可能会遇到了。 我之前做项目时就遇到这个问题,也是总结一篇文章。 比如这种类型的报错 commonjs存在的问题 先讲一下commonjs存在的问题。 CommonJS模块采…...

Springboot指定扫描路径

方式一:通过在启动类的SpringbootApplication中指定包扫描或类扫描 指定需要扫描的包 scanBasePackages{"待扫描包1","待扫描包2", . . . ," "} 指定需要扫描的类 scanBasePackageClasses{类1.class,类2.class,...} 方式二&#xff…...

【Flutter】Dart:环境搭建

Flutter 是一个基于 Dart 的跨平台开发框架,可以帮助我们快速构建移动应用程序。在开始 Flutter 开发之前,我们需要先搭建 Dart 的开发环境,并配置合适的编辑器,比如 VSCode。本教程将引导你一步步完成 Dart 和 Flutter 的环境搭建…...

OpenCV高级图形用户界面(10)创建一个新的窗口函数namedWindow()的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 创建一个窗口。 函数 namedWindow 创建一个可以作为图像和跟踪条占位符的窗口。创建的窗口通过它们的名字来引用。 如果已经存在同名的窗口&am…...

水题四道。

我的 水题四道--题目目录 问题 A: 依次输出第k小整数 代码1 问题 B: 第k小整数(knumber) 代码2 树的统计 代码3 枪声问题 代码4 问题 A: 依次输出第k小整数 现有n个正整数,n≤10000,要求出这n个正整数中的第1小的整数,第2小的整数&#xf…...

upload-labs靶场Pass-05

upload-labs靶场Pass-05 大小写绕过 $deny_ext array(“.php”,“.php5”,“.php4”,“.php3”,“.php2”,“.html”,“.htm”,“.phtml”,“.pht”,“.pHp”,“.pHp5”,“.pHp4”,“.pHp3”,“.pHp2”,“.Html”,“.Htm”,“.pHtml”,“.jsp”,“.jspa”,“.jspx”,“.jsw”…...

【AIGC】解锁高效GPTs:ChatGPT-Builder中系统提示词Prompt的设计与应用

博客主页: [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 💯前言💯系统提示词系统提示词的作用与重要性系统提示词在构建GPTs中的作用结论 💯ChatGPT-Builder系统提示词的详细解读OpenAI为Builder编写的系统提示词系统提示词对…...

【JavaEE初阶】深入理解网络编程—使用UDP协议API实现回显服务器

前言 🌟🌟本期讲解关于TCP/UDP协议的原理理解~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话不…...

C语言复习第3章 函数

目录 一、函数介绍1.1 函数是什么1.2 C语言中函数的分类1.3 函数原型1.4 高内聚 低耦合1.5 C语言main函数的位置 二、函数的参数2.1 实参和形参2.2 函数的参数(实参)可以是表达式2.3 传值与传址(swap函数)2.4 明确形参是实参的临时拷贝2.5 void(如果不写函数返回值 默认是int)2…...

Golang | Leetcode Golang题解之第491题非递减子序列

题目: 题解: var (temp []intans [][]int )func findSubsequences(nums []int) [][]int {ans [][]int{}dfs(0, math.MinInt32, nums)return ans }func dfs(cur, last int, nums []int) {if cur len(nums) {if len(temp) > 2 {t : make([]int, len(…...

conan安装方法简介

因为conan是使用python开发的,所以使用conan需要先安装python环境,这里就不展开python的安装方法了。 conan安装有多种方法,但是比较推荐也是比较简单的一种方法是使用python的pip包管理器安装,相关方法如下(Windows和…...

Java面试指南:Java基础介绍

这是《Java面试指南》系列的第1篇,本篇主要是介绍Java的一些基础内容: 1、Java语言的起源 2、Java EE、Java SE、Java ME介绍 3、Java语言的特点 4、Java和C的区别和联系? 5、面向对象和面向过程的比较 6、Java面向对象的三大特性&#xff1a…...

【mod分享】波斯王子遗忘之沙高清重置,纹理,字体,贴图全部重置,特效增强,支持光追

各位好,今天小编给大家带来一款新的高清重置MOD,本次高清重置的游戏叫《波斯王子:遗忘之沙》。 《波斯王子:遗忘之沙》是由育碧(Ubisoft)开发并发行的一款动作类游戏,于2010年5月18日发行。游戏…...

【计网笔记】物理层

设备 中继器:延长信号传播长度集线器:RJ45接口,无碰撞检测 接口特性 不属于物理层接口规范定义范畴的是(C) A. 接口形状 B. 引脚功能 C. 物理地址 D. 信号电平 传输媒体 导引型媒体 双绞线 减少对相邻导线的电磁…...

《计算机视觉》—— 基于 dlib 库的方法将两张人脸图片进行换脸

声明:此篇文章所用的明星照片只为用于演示代码的效果,无诋毁她人肖像之意 一、案例实现的思想 此案例的核心是基于人脸68个关键点检测模型来实现的,人脸68个关键带点检测后的效果如下: 通过对上图中红色区域的转换,…...

查找与排序-交换排序

交换排序是基于“比较”和“交换”两种操作来实现的排序方法 。 由于选择“比较”的基准元素不同,可将交换排序分为以下两种: 冒泡排序快速排序 一、冒泡排序 1.冒泡排序基本思想 因为其实现与气泡从水中往上冒的过程类似而得名。 每一趟的…...

数据结构与算法:高级数据结构与实际应用

目录 14.1 跳表 14.2 Trie树 14.3 B树与 B树 14.4 其他高级数据结构 总结 数据结构与算法:高级数据结构与实际应用 本章将探讨一些高级数据结构,这些数据结构在提高数据存取效率和解决复杂问题上起到重要作用。这些高级数据结构包括跳表&#xff0…...

【win11】终端/命令提示符/powershell美化

文章目录 1.设置字体1.1. 打开win11的终端/命令提示符/powershell其中之一1.2. 打开终端设置,修改所有终端默认字体为新宋体 2. 修改powershell背景色为蓝色 win11的默认终端/命令提示符/powershell主题风格让人感觉与win10撕裂太大,尤其是字体、背景色&…...

三元损失(Triplet Loss)详解

文章目录 前言一、三元损失的核心思想二、数学公式三、损失函数的解释四、三元损失的优势五、应用场景前言 三元损失(Triplet Loss)是一种广泛应用于度量学习(Metric Learning)中的损失函数,尤其在人脸识别、图像检索等任务中表现优异。三元损失的基本思想是通过定义一个…...

1. 解读DLT698.45-2017通信规约--预连接响应

国家电网有限公司企业标准,面向对象的用电信息数据交换协议DLT698.45-2017 为提高用电信息采集系统的业务适应性、采集效率、安全性和数据溯源性,规范用电信息数据交换协议的通信架构、数据链路层、应用层、接口类与对象标识,制定本标准。 …...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

Spring Boot面试题精选汇总

🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

JVM虚拟机:内存结构、垃圾回收、性能优化

1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA

浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求&#xff0c;本次涉及的主要是收费汇聚交换机的配置&#xff0c;浪潮网络设备在高速项目很少&#xff0c;通…...

uniapp 实现腾讯云IM群文件上传下载功能

UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中&#xff0c;群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS&#xff0c;在uniapp中实现&#xff1a; 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...