直接看 require.cache 和模块加载日志可定位循环引用点,因其是运行时行为而非语法错误;VSCode 静态分析无法检测,需通过 console.log(module.id) 和 require.cache 观察加载顺序与半初始化状态,修复应采用延迟加载或提取共享状态而非删除 require。

直接看 require.cache 和模块加载日志,就能定位循环引用点——这不是语法报错,而是 CommonJS 加载器在运行时卡在半初始化状态。
为什么 Shift+F12 查不到循环引用
“查找所有引用”只分析静态 import/require 字符串,不执行代码,根本看不到实际 require 调用链。循环引用是运行时行为,语言服务器无法推导。你看到的 TypeError: xxx is not a function 或 undefined 属性访问失败,往往就是 A require B、B 又 require A 导致 A 的 module.exports 还没赋完值就被取走了。
- VSCode 的跳转和引用分析对 CommonJS 循环引用完全无感
- 哪怕两个文件互相
require('./a')和require('./b'),编辑器也认为“合法”,不会警告 - 真正出问题时,错误堆栈里通常不包含 require 行,而是下游调用处报 undefined
用 console.log + require.cache 快速确认闭环
在疑似入口(比如 index.js 或启动脚本)顶部加一行日志,然后运行:
console.log('loading', module.id);
观察输出顺序。如果出现类似:
提供端到端的高端代理设置和使用指导,包括代理类型选择、地区定向、访问配置、API密钥验证和监控。
loading ./src/config.js<br>loading ./src/logger.js<br>loading ./src/config.js
就说明闭环已触发。更稳的做法是每个模块开头加:
console.log(module.id, 'exports:', Object.keys(module.exports));
重点看哪个模块被 require 时 module.exports 还是空对象 {} —— 那就是被提前拉进来的“半成品”。
- 别依赖 IDE 的“跳转到定义”,它只扫字符串,不反映真实加载顺序
-
require.cache是关键线索:运行中执行console.log(Object.keys(require.cache)),能列出所有已加载路径;重复出现的路径要警惕 - VSCode 开发者工具(
Ctrl+Shift+P→Developer: Toggle Developer Tools)里直接输require.cache也能查
修复时别删 require,要改加载时机
CommonJS 循环引用不能靠删掉某处 require() 解决,必须打破加载期依赖。两种方案适用场景不同:
-
延迟加载:适合工具类模块(如 logger、db client),把
require()移进函数体,等对方模块初始化完再取。例如:function log(msg) {<br> const config = require('./config');<br> console.log(config.level, msg);<br>} -
提取共享状态:适合配置、常量、基础数据。新建
shared.js,只放纯数据或工厂函数,A 和 B 都require('./shared'),但彼此不再直接 require 对方 - ESM 不会出现这种问题,因为
import是静态绑定;但混用 CommonJS 和 ESM 时,require()仍会触发循环风险
真正难的不是发现循环,而是判断哪条 require 链该延迟、哪部分该拆成共享模块——得结合模块职责看:工具函数优先延迟,配置数据优先抽取。否则修一个地方,另一个地方又崩。

















