资讯动态

本地覆盖与无限debugger:前端调试实战指南

发布时间:2026/10/26 3:08:16 来源:尧图企业网站定制
线上环境跑着一段被压缩得面目全非的脚本你想改一行逻辑验证个猜想结果一打开开发者工具页面就开始疯狂暂停——这是很多人第一次接触将文件覆盖到本地和无限 debugger这两个话题时的真实场景。前者是把远端资源替换成本地副本让你能在真实页面上验证改动后者是页面里被塞进去的反调试代码让你连断点都下不了。这两件事看起来是两套技能实际上在同一个工作流里先把文件覆盖到本地才有可能从源头把 debugger 抹掉先搞定无限 debugger覆盖后的代码才有机会被正常调试。这篇内容适合三类人看做前端开发、需要在真实环境验证改动的同学做前端质量与兼容性排查、需要读懂压缩代码的同学以及正在学习浏览器调试机制的初学者。下面我会把这两块的原理、方案选型、实操步骤、参数细节和踩过的坑全部摊开讲代码可以直接抄步骤可以逐步复现。所有操作都以自己的项目、自己负责的页面、已获得明确授权的测试环境为前提这一点先放在最前面。1. 先理清关系本地覆盖和无限 debugger 是同一件事的两个卡点1.1 本地覆盖到底在解决什么问题很多人对覆盖的理解停留在把文件下载下来改一改但真正有价值的是在浏览器请求链路里做替换。远端服务器返回的main.3f2a.js是一个经过压缩、变量名全被打乱、动辄两三万行的产物你直接读它连函数边界都找不到。把这份文件落到本地、改完之后让浏览器加载本地版本你才获得了在真实页面上下文里验证改动的能力。这件事的价值主要体现在几个场景。第一是线上问题定位线上某个交互不符合预期你在本地仓库改完要重新构建、发布、等缓存一个来回十几分钟而覆盖之后刷新即生效几十秒就能验证一次猜想。第二是第三方资源排查页面引用的某个外部脚本行为异常你没法改对方的代码但可以在本地替换一份加了日志的副本来观察调用顺序。第三是压缩代码的可读性改造把压缩产物落到本地用格式化工具展开再替换回去运行时的堆栈行号就对得上了排查报错会轻松很多。这里有个容易被忽略的前提覆盖不是下载文件这么简单它要求浏览器在发起请求时跳过网络、直接读本地或者读取本地之后仍然走原来的响应头处理流程。这两者的差别很大前者快但可能丢 header后者慢一点但行为更接近真实。选哪种取决于你要验证的是逻辑还是网络行为。1.2 无限 debugger 为什么让人寸步难行debugger语句本身是个很朴素的东西执行到它就暂停等你按继续。但一旦它被塞进setInterval的回调里性质就变了——你按一次继续几十毫秒后它又来一次如此往复开发者工具的面板永远处于暂停态你连 Console 都敲不进去更别说在别的文件里下断点。这类代码的目的大多不是防君子而是抬高分析成本。常见做法有几种把debugger放在一个每 50 到 200 毫秒触发一次的定时器里用Function构造器把debugger藏进字符串绕开静态扫描在页面初始化时检测开发者工具是否打开一旦判定为打开就进入死循环。它们的共同点是让调试行为本身变成一种惩罚。要理解为什么这些手段有效得先明白浏览器的断点机制断点命中后整个 JS 执行线程会停下来页面渲染、事件响应、定时器全部冻结。这意味着你没法边跑边看只能停下来再看。反调试代码正是利用了这个特性——它不需要真的阻止你只需要让你每次都停在一个无意义的位置上消耗你的耐心。1.3 方案选型三个维度决定你该用哪一套面对覆盖和过断点这两个需求市面上能用的手段不少但并不是每一种都适合你的场景。我一般从三个维度判断改动范围单文件还是整站、是否需要在页面脚本执行前介入这决定了很多 Hook 方案能不能用、恢复成本改完能不能一键还原。方案改动范围介入时机上手成本典型坑点开发者工具 Overrides单个请求请求发出时替换响应低换浏览器配置文件后映射丢失抓包工具本地映射单文件或整目录请求发出时替换响应中需要提前处理好证书信任hosts 重定向 本地静态服务整站从请求发起就接管中高跨域、Cookie 域、HTTPS 证书都要自己配运行时 Hook全局函数脚本执行后低时机太晚抢不到最早的那次调用覆盖 HTML 注入内联脚本整页页面解析最早期中需要同步维护 HTML 副本这张表建议先存下来。实际工作中我用得最多的是第一行和第五行的组合用 Overrides 替换脚本文件同时用 Overrides 替换 HTML 在最前面插一段 Hook 代码两个动作配合起来绝大多数反调试都能绕过去。选择方案时还有一条经验优先选离浏览器原生机制最近的那个。Overrides 是浏览器自己实现的功能它替换响应时保留了完整的响应处理流程包括 MIME 类型判断、字符集解析、CORS 检查行为最接近真实加载。而自己搭本地服务接管域名虽然控制力最强但你要自己处理 HTTPS、跨域、Cookie出问题时排查成本反而更高。2. 把远端文件落到本地四种覆盖姿势的实操2.1 开发者工具 Overrides改动最小、恢复最快这是我最常用的方式因为它不需要装任何东西也不需要处理证书。整个流程分三步第一次配置大概两分钟之后每次覆盖只要几秒钟。第一步打开开发者工具的 Sources 面板找到左侧的 Overrides 标签有的版本在面板右上角的更多菜单里。点击选择文件夹用于替代在你自己的磁盘上建一个专门的空目录比如D:\debug\overrides。这个目录建议单独建不要和项目源码混在一起因为浏览器会往里面写一堆按域名分层的文件。选完之后浏览器会弹一次权限确认必须点允许否则写不进去。第二步勾选启用本地替代。这一步很多人会漏结果后面右键菜单是灰的。第三步切到 Network 面板刷新页面找到你要改的那个请求右键选替代内容Override content。此时浏览器会把响应体写到刚才那个目录里并在 Sources 面板的 Overrides 树下生成对应条目。目录结构是按域名含端口建一级文件夹后面按 URL 路径逐级落盘比如app.test/static/js/main.3f2a.js。你在 Overrides 树里直接编辑按 CtrlS 保存刷新页面就加载你改过的版本了。这里有个细节值得说你改的是文件内容不是响应头。如果原响应带Content-Encoding: gzip浏览器在写入本地之前已经解压过了所以你看到的是一份可读文本改完保存后浏览器会按原响应的 MIME 类型重新处理。这意味着你不必关心压缩问题但也意味着你不能通过改文件来伪造响应头。需要改 header 的场景比如临时去掉Cache-Control得用面板里单独的替代标题功能或者干脆换方案。注意Overrides 的映射关系保存在浏览器的用户配置里清理浏览器数据、切换配置文件、重装浏览器都可能丢失。重要的覆盖改动记得把本地目录备份一份。2.2 抓包调试工具的本地映射适合整目录替换当你需要替换的不是一个文件而是一整个目录比如把static/js/下十几个 chunk 全部换成本地构建产物用 Overrides 一个个点就太慢了。这时候本地映射更合适。常见抓包调试工具都有本地映射或者Map Local之类的功能规则本质上是一条匹配表达式加一个本地路径。配置的时候要注意匹配表达式的写法。绝对路径匹配最直接比如把https://app.test/static/js/main.3f2a.js直接映射到D:\work\dist\main.js。但线上文件名带 hash每次发版都变这时候就要用正则或者通配符匹配/static/js/(.*)\.js映射到本地目录D:\work\dist\$1.js让工具自己把捕获组填进去。这个写法能省掉大量重复劳动。第二个要注意的是目录映射的落盘规则。有的工具支持映射到目录它会保持 URL 后面的相对路径结构去找本地文件有的只支持单文件映射。选之前先确认清楚否则你会遇到规则明明配了但返回 404的情况。第三个是证书。走 HTTPS 的映射需要工具自己的根证书被系统信任这一步在初始配置时做一次就行。我的习惯是专门用一个独立的浏览器配置文件做调试把证书装在那个配置文件里不影响日常使用的主配置文件。提示本地映射的优先级通常高于浏览器缓存。如果你发现改了本地文件但页面没变先检查是不是映射规则没命中而不是去清缓存。2.3 hosts 重定向加本地静态服务整站可控这个方案的控制力最强成本也最高。思路是把目标域名解析到本地回环地址然后在本地起一个静态服务监听对应端口把整个站点的静态资源都接管过来。适合需要长时间反复调试、且改动面很大的场景。具体做法是在系统的 hosts 文件里加一行把目标域名指向127.0.0.1然后在本地用任意静态服务器把目录跑起来端口对齐原站。麻烦的地方在于 HTTPS你需要给这个域名签一张本地证书并让系统信任否则页面会报证书错误脚本加载全部失败。自己签证书这件事用常见工具生成一份自签名证书导入系统信任列表就行但要注意证书的 SAN 里必须包含你用的域名只写 common name 在新版浏览器里是不认的。这个方案还有个隐性坑Cookie 的作用域和 SameSite 属性。整站接管之后原本由服务端下发的 Cookie 没有了页面里依赖登录态的逻辑会全部失效。如果你的调试需要登录态得手动在本地服务里模拟这些 Cookie或者干脆放弃整站接管退回单文件覆盖。我个人的判断标准是改动文件数超过十个、且需要反复调试超过半天才考虑这个方案否则前两种更快。2.4 覆盖之后必须检查的四件事覆盖做完不等于生效我踩过的坑里有一半是以为覆盖了其实没有。下面这四项建议每次覆盖后都过一遍。第一确认请求真的走了本地。在 Network 面板看那个请求如果来源列显示替代或者有个特殊的标记说明命中如果还是正常的网络请求说明没生效。这一步最容易被忽略因为页面看起来好像变了其实是你上次改的缓存还在。第二确认 MIME 类型没被改坏。有些工具在本地映射时会根据文件扩展名重新推断 Content-Type如果你把.js存成了.txt再映射回去浏览器会因为 MIME 不匹配拒绝执行控制台会直接报错。这个错误的提示很明确看到就知道怎么回事。第三确认字符集一致。压缩产物里可能有中文注释或者中文字符串如果本地文件存成了 GBK 而原响应是 UTF-8会出现乱码甚至语法错误。编辑器右下角看一眼编码养成习惯。第四确认 source map 还对得上。如果你改动了代码行数浏览器加载的.map文件里的行号映射就全乱了报错堆栈会指向莫名其妙的位置。调试压缩代码时要么同步更新 map要么临时关掉 source map 加载别让错误的映射误导你。提示判断覆盖是否生效最快的办法是在你改的那一行故意写一个语法错误。刷新后如果控制台立刻报错说明本地文件确实被加载了如果毫无反应问题出在覆盖链路而不是你的代码。3. 无限 debugger 的四种生成方式看不懂就别急着下手3.1 定时器型setInterval 循环里塞 debugger这是出现频率最高的一种。代码形态非常简单一个setInterval或者递归的setTimeout回调里就一句debugger。有的会加一点伪装比如用字符串拼接、用变量名混淆但本质不变。// 典型形态定时器回调里直接放 debugger setInterval(function () { debugger; }, 100); // 稍微伪装一点的用数组下标或者字符串拼接 var _0x1a2b [debugger]; setInterval(function () { eval(_0x1a2b[0]); }, 200);为什么它难缠因为频率高。间隔 100 毫秒意味着你按一次继续最多 0.1 秒后又被停住。就算你手速再快也没法在两次暂停之间完成任何有意义的操作。更麻烦的是这类代码往往不只有一个可能有三五个定时器同时在跑你停掉一个还有别的。识别方法很直接在 Sources 面板打开事件监听器断点里的 Timer 分类或者直接在 Call Stack 里看暂停位置的上层调用。如果每次暂停的调用栈都是(anonymous) setInterval那就是定时器型无疑。3.2 构造器型Function 与 eval 动态拼接第二种是把debugger藏进字符串用Function构造器或者eval动态执行。这样在源码里搜debugger关键字是搜不到的静态扫描会漏掉。// Function 构造器型 var fn new Function(debugger); setInterval(fn, 150); // eval 型 setInterval(function () { eval(deb ugger); }, 150);这类代码的特点是调用栈里会出现Function或者eval的帧。排查时在 Call Stack 里看到eval VM1234:1这种虚拟文件名的帧基本就能确定。它的难点在于你没法通过搜索源码定位只能通过运行时行为反推。顺带说一句Function构造器有个特性它创建的函数作用域是全局的拿不到外层的闭包变量。所以有些代码会配合eval使用因为eval能访问当前作用域。两种混用的版本排查起来更费劲得看调用栈里两层都出现才行。3.3 检测型时间差与对象陷阱第三种不直接放debugger而是检测你是否打开了开发者工具检测到了再触发反制措施。常见手段有两类。一类是时间差检测。原理是debugger语句命中后执行会暂停performance.now()的差值会远大于正常执行时间。于是代码在前后各取一次时间戳如果差值超过阈值比如 100 毫秒就判定为调试状态。var t1 performance.now(); debugger; var t2 performance.now(); if (t2 - t1 100) { // 判定为打开了调试工具进入死循环或者跳转 while (true) {} }另一类是对象陷阱。利用console.log打印对象时开发者工具会去读对象的属性这一特性给某个属性定义 getter一旦被读取就说明控制台打开了。var el new Image(); Object.defineProperty(el, id, { get: function () { // 这里被触发说明控制台在展开这个对象 document.body.innerHTML ; } }); console.log(el);这两类的应对思路完全不同第一类你把debugger抹掉就行因为检测的前提没了第二类你得把整段检测逻辑删掉光删debugger没用performance.now()的差值依然可能触发如果你在其他地方下了断点。3.4 混淆器批量插入片段级 debugger第四种不是无限的而是量大。一些混淆工具会在每几十行插一句debugger一个文件里可能有上百处。单次执行只停一次但你按继续之后很快又会在下一个位置停住体验上和无限 debugger 差不多。识别特征是每次暂停的位置都不同但都在同一个文件里。这种情况用永不在此暂停一条条点会点到崩溃正确做法是把这个文件整体设为黑盒脚本或者干脆用本地覆盖把文件里所有debugger批量替换掉。4. 逐类击破过无限 debugger 的实操手法4.1 三板斧先顶住停用断点、永不在此暂停、条件断点遇到无限 debugger先别急着改代码开发者工具自带的功能往往就够了。这三招按顺序试一遍能解决大概六成的情况。第一招停用断点。快捷键是 CtrlF8或者点 Sources 面板顶部的那个停用断点按钮。它的作用是让所有断点包括代码里写死的debugger语句都不生效。这是最快的一招缺点是它把所有断点都关了你需要精确调试时得再打开。第二招永不在此暂停。在暂停状态下在代码行号上右键选永不在此暂停Never pause here。这一招只对当前这一行生效好处是精准不影响其他地方下断点。如果页面里只有一处debugger这一招就够了而且可以保留其他断点的能力。第三招条件断点。同样在行号上右键选添加条件断点然后输入false。效果和永不在此暂停类似但更灵活——你可以输入任何恒假表达式比如1 2。这一招的价值在于某些场景下永不在此暂停不生效比如代码被 source map 重写之后行号对不上条件断点反而能命中。心得如果debugger在压缩代码的一行里出现多次混淆工具会把多句代码挤在一行单行设置永不在此暂停会把整行都跳过可能导致你漏掉同行的其他逻辑。这种情况我会先格式化代码再逐个位置处理。4.2 黑盒脚本与忽略清单让断点根本不会停当反调试代码集中在一个文件里时把这个文件整体设为黑盒Blackbox script是效率最高的做法。设为黑盒之后执行到这个文件的任何位置都不会暂停包括debugger语句、异常、以及你手动下的断点。设置方式有两种在 Sources 面板的文件树里右键该文件选添加到忽略清单或者在设置面板里找到忽略清单Ignore List手动填匹配规则。规则支持通配符比如.*vendor.*\.js就能把路径里带 vendor 的文件全部忽略。这一招的关键价值在于它会连带忽略这个文件内部的异常堆栈。反调试代码常常在判定成功之后抛一个异常你没设黑盒的时候断点会停在这里设了之后异常会静默跳过执行继续。这是我个人最常用的一招因为它不需要改任何代码也不需要每次重新配置——忽略清单是持久化的配一次长期有效。需要注意的地方是设了黑盒之后你在这份代码里下的断点会提示已验证断点未启用这不是 bug是设计如此。想调试这个文件的时候把它从忽略清单里移除即可。4.3 从源头改本地覆盖把 debugger 抹掉功能性的绕行手段终究是治标真正干净的做法是把debugger从代码里删掉。这正好接上第 2 节讲的覆盖方案。整个动作分两步。第一步用 Overrides 把含debugger的文件落到本地。第二步在本地文件里做批量替换。如果文件不大直接用编辑器的全局替换把debugger换成void 0或者一个空语句;。如果文件很大几万行用命令行更省事# 把文件里所有的 debugger 语句替换成空语句 # 注意只替换独立语句避免误伤字符串里包含 debugger 的内容 sed -i s/^[[:space:]]*debugger;[[:space:]]*$/\;/g ./overrides/app.test/static/js/main.3f2a.js # 替换前先备份一份方便对比 cp ./overrides/app.test/static/js/main.3f2a.js ./main.3f2a.js.bak命令行替换有个风险字符串里的debugger会被误伤。比如代码里有个日志文案写着console.log(debugger detected)被替换之后文案就变了。所以稳妥的做法是用编辑器打开确认每一个匹配位置都在语句上下文中再执行替换。压缩代码里这种情况很常见宁可慢一点也别一把梭。替换完之后刷新页面如果debugger不再触发说明还有别处藏了。这时候回到 4.1 的排查流程逐个定位。对于 3.3 节讲的检测型代码光删debugger不够得把整段检测逻辑处理掉。稳妥的做法是把判断条件改成恒假比如把if (t2 - t1 100)改成if (false)这样检测逻辑还在但永远不触发不会因为删多了导致后面的代码报错。4.4 运行时 Hook重写 setInterval、Function、eval如果你不想维护本地文件副本或者覆盖目录会被清空运行时 Hook 是更轻的选择。核心思路是在页面脚本执行之前把全局函数替换成自己的版本遇到可疑内容就跳过。先看setInterval的 Hook(function () { var rawSetInterval window.setInterval; var rawSetTimeout window.setTimeout; function isSuspicious(fn) { if (typeof fn ! function) { // 传字符串的情况也要检查 return typeof fn string fn.includes(debugger); } try { return fn.toString().includes(debugger); } catch (e) { return false; } } window.setInterval function (fn, delay) { if (isSuspicious(fn)) { console.warn([hook] blocked suspicious setInterval); return 0; } var args Array.prototype.slice.call(arguments); return rawSetInterval.apply(window, args); }; window.setTimeout function (fn, delay) { if (isSuspicious(fn)) { console.warn([hook] blocked suspicious setTimeout); return 0; } var args Array.prototype.slice.call(arguments); return rawSetTimeout.apply(window, args); }; })();这段代码能拦掉大部分定时器型的反调试。注意返回0而不是null因为有些代码会拿返回值去clearInterval返回null在某些实现里会抛错。返回一个数字虽然清不掉但至少不会崩。再看Function构造器的 Hook。这里建议用Proxy因为直接赋值window.Function ...会破坏Function.prototype的引用链有些库会检测这个。(function () { var RawFunction window.Function; window.Function new Proxy(RawFunction, { construct: function (target, args) { var body args[args.length - 1]; if (typeof body string body.includes(debugger)) { console.warn([hook] sanitized Function body); args[args.length - 1] body.replace(/\bdebugger\b/g, ;); } return Reflect.construct(target, args); }, apply: function (target, thisArg, args) { var body args[args.length - 1]; if (typeof body string body.includes(debugger)) { args[args.length - 1] body.replace(/\bdebugger\b/g, ;); } return Reflect.apply(target, thisArg, args); } }); })();eval的 Hook 类似但要注意eval有两种调用方式直接调用eval(...)属于当前作用域间接调用window.eval(...)属于全局作用域。重写window.eval只能拦到间接调用直接调用的拦不住。这是个已知限制实际排查时如果发现 Hook 生效了但 debugger 还在多半是遇到了直接eval这种情况只能退回覆盖方案。4.5 注入时机才是难点三种让脚本抢在页面之前跑的办法上面那些 Hook 代码写起来不难难的是什么时候执行。如果页面在head里就执行了反调试代码而你的 Hook 是在 Console 里手动粘贴的那已经晚了——定时器已经注册拦截器再装上也没用。第一种办法覆盖 HTML 注入内联脚本。用 Overrides 把 HTML 落到本地在head的第一个位置插入一段script把 Hook 代码内联进去。这是最可靠的方式因为 HTML 是页面的起点插在最前面就一定比后面所有脚本都早。缺点是每换一个页面就得改一次 HTML 副本维护成本略高。第二种办法用开发者工具的代码片段配合在页面加载时运行。把你的 Hook 代码存成一个代码片段Snippet然后在 Sources 面板的代码片段列表里右键选在页面加载时运行Run on page load。这个选项会让片段在每次页面刷新时自动执行而且时机是在文档开始解析之后、页面脚本执行之前的窗口期。绝大多数情况下够用。第三种办法用浏览器扩展的document_start注入。如果你经常调试同一类页面写一个极简的扩展在content_scripts里配置run_at: document_start就能保证注入时机最早。这个方案成本最高但一次配置长期受益适合把调试流程固化下来的人。注入方式时机维护成本适用频率覆盖 HTML 插内联脚本页面解析最早期中单页面反复调试代码片段页面加载时运行页面脚本执行前低通用场景首选扩展document_start注入最早高长期固定流程Console 手动粘贴最晚最低只做临时验证提醒代码片段页面加载时运行这个选项有时会被忽略因为它藏在右键菜单里。如果你的片段没有自动执行先检查这个开关是不是打开了。5. 排查实录那些覆盖不生效、断点停不下来的坑5.1 覆盖生效了但页面还是旧代码这是问得最多的一个问题。明明本地文件改好了Network 面板也显示走了替代但页面行为没变。我遇到过的情况大概有五类。第一类是服务工作线程缓存。如果页面注册了 Service Worker它会在网络请求之前拦截返回自己缓存的那份。这时候你覆盖的响应根本到不了页面。排查方式是在 Application 面板里看 Service Worker 的状态调试期间直接注销掉或者在 Network 面板勾选绕过服务工作线程。第二类是请求 URL 带查询串。你覆盖的是main.js实际请求的是main.js?v20240501两者在覆盖记录里是不同条目匹配不上。解决办法是在 Network 面板按实际请求的完整 URL 去右键覆盖别自己猜。第三类是多标签页共享。你在 A 标签页改了覆盖B 标签页还是旧的运行状态。刷新 B 标签页就好但很多人会以为是覆盖失效。第四类是iframe 里的资源。主页面走了覆盖但内嵌的 iframe 是另一个文档它的请求需要单独覆盖。在 Network 面板的过滤器里关掉隐藏 iframe 请求才能看到。第五类是浏览器配置隔离。如果你用多个浏览器配置文件切换调试覆盖目录是绑定在配置文件上的换个配置文件就看不到之前的映射了。5.2 debugger 关了又冒出来怎么定位源头关掉一处debugger之后又被停住说明源头不止一个。定位方法我一般按这个顺序走。第一步看暂停时的 Call Stack。最上面一帧是debugger所在位置往下一两帧就能看到它是被谁调用的。如果是setInterval或者setTimeout的回调那就是定时器型如果是Function或者eval的虚拟帧那就是构造器型。第二步在 Sources 面板按 CtrlShiftF 做全局搜索搜debugger关键字。压缩代码里可能搜不到被混淆了但能搜到一部分。搜到之后逐个处理。第三步如果全局搜索一无所获就用事件监听器断点里的 Timer 分类。勾上之后每次有定时器注册都会暂停一次你能看到是谁注册的、传了什么函数。这一招很吵但很有效定位到具体代码后立刻取消勾选。第四步如果以上都不行用performance.now()打点。在 Console 里跑一段采样代码每隔 16 毫秒记录一次时间戳如果出现超过 100 毫秒的跳跃说明期间发生了暂停。配合时间点去对照代码能缩小范围。5.3 常见问题速查表现象可能原因处理方式覆盖后页面无变化服务工作线程缓存注销 SW 或勾选绕过覆盖后控制台报 MIME 错误本地文件扩展名或 Content-Type 不对改回.js并确认响应类型覆盖后中文乱码文件编码与原响应不一致统一存为 UTF-8局部断点不生效文件在忽略清单里从忽略清单移除按继续后立刻又停存在多个定时器型 debugger逐个定位并本地覆盖处理Hook 装上但没用注入时机晚于页面脚本改用覆盖 HTML 或代码片段自动运行条件断点输入false仍停source map 导致行号错位关掉 source map 再设iframe 里的 debugger 拦不住未切换执行上下文在框架下拉里切到对应文档工作线程里的 debugger 拦不住未在 Worker 上下文操作在 Sources 面板切到 Worker 线程改了本地文件但报错堆栈错乱source map 行号未同步临时禁用 map 加载这张表我建议打印出来贴在显示器边上遇到问题先对一遍能省掉大量瞎猜的时间。6. 写在实操之后的几点体会6.1 边界感哪些环境可以动哪些不要碰技术本身是中性的但使用场景有明确边界。上面这些手段我只会用在三种情况下自己负责的项目出问题时需要快速定位明确授权过的测试环境对方书面确认了测试范围和方式纯粹的学习研究用自己搭的模拟页面来验证机制不碰任何线上系统。反过来说凡是涉及他人系统、未获授权、可能影响他人正常使用的场景一律不碰。这不是技术能力问题是基本的职业判断。很多所谓的技术交流最后变成了对他人系统的干扰这种事情的代价远比学到的那点技巧大。另外提醒一句调试期间改的东西记得还原。覆盖目录里的文件、忽略清单里的规则、hosts 文件里的条目用完就清理。我见过有人调完之后忘了还原 hosts第二天上班发现整个团队的开发环境都指向了错误地址排查了半天才想起来。6.2 三个提高效率的小习惯第一个习惯给覆盖目录建一个固定的结构。我一般按项目名分一级目录下面再按日期或者问题编号建子目录每个目录里放一个README记录这次改了什么、为什么改、什么时候可以删。听上去有点小题大做但调试工作往往是改十次只留一次没有记录的话两天后就完全想不起来当初为什么这么改。第二个习惯把常用的 Hook 代码存成代码片段。setInterval拦截、Function拦截、eval拦截这三段代码我存了快两年每次遇到新页面直接调出来改两行就能用。省下来的时间远比当初写这几段代码的时间多。第三个习惯先判断类型再动手。新手最常见的错误是一上来就开始删debugger删了半天发现是检测型代码删了也没用。正确的顺序是先花三十秒看调用栈判断是定时器型、构造器型、检测型还是批量型然后直接选对应的解法。这三十秒能省掉后面半小时的瞎折腾。最后一个我自己踩过的坑分享出来有一次调一个页面debugger怎么都关不掉我把能想到的办法全试了一遍最后发现是浏览器里装了某个调试辅助扩展它自己在注入代码。排查这类怎么都搞不定的问题时先开一个干净的浏览器配置文件试试如果问题消失那问题就不在页面本身。这个思路在很多领域都通用——当你怀疑是目标的问题时先换一个无干扰的环境验证一遍。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑