layui.use死循环表现为卡住不执行回调、无报错无日志、JS阻塞,根源是模块加载器在解析依赖链时无限递归;定位需在layui.js中加log观察模块名是否反复出现相同序列。

layui.use 死循环的典型表现和定位方式
死循环发生时,layui.use 会卡住不执行回调,控制台既无报错也无加载完成日志,页面 JS 阻塞、模块迟迟不就绪。这不是浏览器崩溃,而是 layui 内部模块加载器在解析依赖链时陷入无限递归。
排查第一步不是改代码,而是打开 layui 源码中的 layui.js(非压缩版),在 layui.use 调用链中找到 laymod.add 和 laymod.get 相关逻辑,在模块注册/查找处加 console.log(modName, '→', deps),观察模块名是否反复出现相同序列(比如 A → B → C → A)。
常见循环依赖场景及修复动作
layui 的模块系统不支持循环依赖,且不会自动报错提示,只会静默卡死。真实项目中最常出现在以下三类组合中:
-
form模块里 requirelaydate,而laydate又内部 requireform(旧版 laydate v1.1 确实存在该行为) - 自定义模块
utilsrequiretable,而table的某个扩展插件又 requireutils -
upload+element+ 自定义弹窗模块之间通过layui.use互相触发加载
修复原则:把「被多个模块共用」的逻辑抽成纯函数或独立模块,不带任何 layui 模块依赖;避免在 define 回调体内调用 layui.use —— 这是最大雷区。
如何用 lazy require 绕过初始化期循环
如果确实需要某模块在运行时才用(比如点击后才打开日期选择器),就不要在 define 顶层写 layui.use(['laydate'], callback),而是把加载延迟到事件回调里:
define(function(exports) {
// ✅ 正确:只声明,不立即加载
var laydate;
$('#open-date').on('click', function() {
// ✅ 延迟到点击时再加载,避开 define 期依赖图构建
layui.use('laydate', function(laydateMod) {
laydate = laydateMod;
laydate.render({elem: '#date-input'});
});
});
exports.init = function() {};
});
这样 laydate 不会进入当前模块的静态依赖列表,layui 加载器就不会把它纳入拓扑排序,也就断开了循环链。
检查第三方扩展模块是否偷偷 require 了你自己的模块
很多开发者忽略一点:自己写的 xxx.js 被别人当成 layui 模块引用时,如果它内部写了 layui.use(['myUtils'], ...),而 myUtils 又 require 了 xxx,就构成隐式循环。这类问题极难发现,因为错误不在你主动写的 layui.use 调用里。
建议做法:
- 全局搜索项目中所有
layui.use\([^\)]*['"]my(正则匹配),确认每个被 require 的模块都不存在反向引用 - 对所有第三方扩展模块(尤其是非官方
lay-module目录下的),逐个检查其源码里的layui.use和require调用 - 临时注释掉疑似模块,用排除法验证是否恢复加载
真正棘手的永远不是 A 和 B 明面上的相互 require,而是 C 在无人注意时把 A 和 B 黏成了环 —— 所以查依赖不能只盯自己写的模块。











