接到这个标题我就笑了,因为“无限 debugger”这个名字起得相当贴切。你在开发者工具里刚按了一下 F12,控制台立刻开始无休止地停在 debugger 语句上,就像有个无形的胶水把断点粘在你面前,你点一次“继续运行”,它马上再停一次,哪怕你选中了“Deactivate breakpoints”,有的页面照样能把你卡得动弹不得。
这篇东西我不想写成科普文档,更想用实战踩坑的记录来讲。很多前端页面,尤其是数据敏感度高的平台,都会在关键脚本里埋反调试逻辑,无限 debugger 就是最常见也最恶心的一种。它的原理并不复杂,核心就是用 debugger 语句加定时器,形成一种“你打开控制台就疯狂暂停”的效果,等于用调试器本身来对抗调试。你会用到这套办法的场景,一般是做前端性能分析、数据接口联调、或者给自己负责的站点做安全检测,偶尔也会在研究别人页面的时候撞上它。这篇文章就是把这套“只要打开工具就被人按暂停键”的问题,用一个相对极端的手段解决掉,让你能安安静静地看代码,而不是跟弹窗互怼。
整体思路我先放在前面:别去跟它的执行流缠斗,也不要试图一行行去删代码。最彻底的邪修方案,是在代码运行之前就改写掉它的触发机制,无论它是用 setInterval 循环、用构造函数做递归、还是把 debugger 塞进 getter 里面,都可以在脚本真正运行前拦截住。下面把我实际跑通的几条路线全部拆开讲,从傻瓜式到手工定制版都有,你可以直接照抄。
1. 先搞清楚碰上的是哪种“无限 debugger”
1.1 两种常见的插桩方式:正计时与轮询
我遇到过的无限 debugger,绝大多数可以归成两类。第一种最原始,也最好认,就是在脚本里写了一个 setInterval 定时器,每 100 毫秒或者 500 毫秒执行一次 debugger 语句。这种方式就像每隔一会儿往你的调试器里丢一块绊脚石,你刚把断点按掉,下一次定时器又触发了。它不需要太高深的技术,一行 setInterval 加一个 debugger 就能写得出来,但效果很稳定,几乎对所有刚打开 DevTools 的人都有效。
第二种稍微隐蔽一点,它会通过构造一个匿名函数、或者通过 try/catch 包住的逻辑触发 debugger,比如:
javascript复制(function() {
function check() {
debugger;
setTimeout(check, 100);
}
check();
})();
这段代码不管你怎么点“跳过下一个断点”,它都会在无限递归里反复暂停。这里的核心点在于,它不是靠浏览器外部的监听来判断你有没有打开控制台,而是直接在页面主线程里建立一个永不停歇的循环。只要你打开了开发者工具并停在脚本面板,它就会不断中断执行。很多人会把无限 debugger 等同于“反爬虫”,但其实它的真正目标是“反调试”,也就是说,它是想把那些试图通过动态分析页面代码的人劝退。
1.2 debugger 触发的完整链路到底长什么样
要真正解决它,我建议先搞清楚这段逻辑是怎么被加载的。一个典型的反调试脚本会藏得比较深,比如它是通过某个 JS 文件动态注入的,甚至是通过字符串拼接构造出来再 eval 执行的。你直接在 Sources 面板里全局搜 debugger,往往搜不出来,因为源码里可能写的是 "debu" + "gger",又或者被混淆工具重命名成了别的函数名。
常规页面会走这样的链路:
- HTML 加载一个入口脚本,通常是压缩过的 bundle 文件。
- bundle 里通过自执行函数生成了 debugger 逻辑,并注册了定时器。
- 定时器回调触发调试器中断,中断点落在代码位置 A。
- 当你在当前位置右键选择“Never pause here”,它会跳到下一个被混淆过的 debugger 位置 B。
所以你会发现,手动一个个去禁用断点根本不可能,因为代码可能有好几处 debugger,还套在多层循环里面。真正有效的做法不是“在断点处反制”,而是“在源头让这条链路断掉”。这个思路就像对付一个每隔几秒就给你发骚扰消息的机器人,你拉黑它当前账号没用,因为它还能换新的号继续发,你得直接关掉它的发送接口。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发者工具里的“正面硬刚”方法
2.1 掌握 DevTools 的断点管理面板
如果你是临时调试、不想马上写脚本改动页面,那 DevTools 里自带的几个断点控制功能可以先救急。打开 Source 面板,右侧的 Call Stack 区域下面有一个 Breakpoints 列表。所有目前因为 debugger 语句而触发的断点都会显示在里面。比较基础的操作是,在断点那一行上右键,可以选择“Edit breakpoint”,把它改成一个永远不会成立的条件,比如 1 === 2。这样,虽然源码里还有 debugger,但它实际不会中断。
这个方法很简单,但我必须提醒你,它只对“当前断点”生效。如果对方写的是多段式子、多文件交叉递归那种,你每遇到一个新的 debugger 就要手动去改一次。页面一旦刷新,所有断点全部重置,你得重新来一轮。所以这个方案适合那种只是为了快速看一眼变量值、不想大动干戈的场合,真用它对付严防死守的站点,你会按到怀疑人生。
我自己的操作习惯是,如果只想临时跳过,优先用 Conditional breakpoint。在断点行右击,选 Add conditional breakpoint,输入一个 false 值,这个断点直接失效。如果不希望 DevTools 进入某个第三方或者混淆代码内部,还可以把整个脚本目录加入 Blackboxing,这样调试时它会直接跳过这些文件,不再被 debugger 干扰。Blackboxing 的位置在 DevTools 设置里的 “Ignore List” 或 “Blackboxing” 区域,添加对应的脚本 URL 或路径即可。
2.2 用页面自己注入的断点拖住它的定时器
这一节这个方法有点“以毒攻毒”的意思。当你发现某个脚本在疯狂触发 debugger 时,可以先在代码里主动加一个不会自动恢复的断点,比如在定时器回调的上方打断点。等程序停在你的断点位置,你再看右侧作用域里有没有定时器 ID,或者直接在控制台里执行 clearInterval / clearTimeout。
操作顺序是这样的:
- 打开 Sources 面板,找到报出 debugger 的那个文件,看是不是有
setInterval在循环触发。 - 在它的定时器回调函数第一行加一个普通断点。
- 等断点命中后,在 C
