ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

前端调试九个救命偏方:从console.log到浏览器摄像头式追踪

前端调试九个救命偏方:从console.log到浏览器摄像头式追踪 调试前端代码这事十个项目有九个都是console.log打天下。这话听着扎心但真到了线上样式被莫名改掉、接口返回值不对、状态不知道被谁动过这种场景光靠console.log就是在浪费生命。我干了这么多年前端踩过的坑比写的代码还多今天把这 9 个调试偏方一次说透。它们不是教科书里的标准流程全是浏览器里真正能救命的招尤其适合那些看起来毫无头绪的 bug。话不多说先从思路说起。1. 先转变一下调试思路别再用 console.log 猜答案1.1 普通日志的问题只能看到值看不到来源很多人的调试习惯是出 bug 先加一行console.log刷新看结果不对再加一行再来。这个循环在简单场景没问题但一旦涉及异步、状态共享、第三方库干扰打点-刷新-猜测的节奏立刻崩盘。因为console.log只能告诉你此刻的值是什么它不能告诉你这个值是从哪来的谁改了它为什么偏偏在这个时间点变了。尤其是线上 bug你根本没有条件在代码里到处插桩更不可能靠反复加日志修好一个偶现问题。1.2 偏方的底层逻辑给浏览器装上六个摄像头所以这 9 个偏方的前提是思路转变调试不是猜答案而是建立观察点。你要把浏览器当成一个实验室在关键节点上放好检测仪器然后在问题发生的瞬间顺着调用栈往回追。九个偏方按用途分三类日志断点类1-3让输出信息更精细页面修该类4-6直接把线上代码当成本地代码改数据监控类7-9让看不见的变化通通现形。这套组合本质上就是在代码执行、请求流量、DOM 变化、状态变更、渲染性能这些维度各装了一个摄像头出问题时回放录像就行。2. 偏方 1-3日志和断点用出仪器的效果2.1 偏方 1console 全家桶别再只会 logconsole.log能打对象但对象层级一深打出来就要手动展开展开完还得自己数。console.table直接把数组里的一堆对象输出成表格地图数据、选项列表、接口返回值一眼就能看到哪一行不对。console.trace在函数入口打一行能把完整调用链拉出来——很多时候你只看见这里被调用了但不知道是谁在什么流程里调用的用 trace 一秒定位。console.time和console.timeEnd是同一件事的两端包住一段耗时逻辑输出执行时间比自己new Date相减方便得多精度也更稳。console.assert是带条件的日志条件为假时才输出适合在循环里只打异常情况防止日志刷屏。console.group可以把一堆日志折叠成一组配合上面的 table、trace 一起用控制台不会乱成一片。注意console.time/timeEnd的名字必须一致大小写也不能错否则直接报类似 Timer xxx already exists 的提示。另一个坑是console.table在对象 key 顺序不稳定时展示顺序可能和预期不同建议先Object.keys整理一下再打。2.2 偏方 2条件断点日志断点精准卡住异常瞬间普通断点一打刷新页面就停停的位置经常是你猜的不是问题真正发生的位置。条件断点好就好在按条件停右键行号选择 Add conditional breakpoint填入一个布尔表达式比如i 5 item.status error只有满足条件时才会断下来。这个功能在排查大量循环、批量渲染、列表筛选这类场景时省的不是一点半点。日志断点Logpoint更绝不打断执行只在断点位置把指定值打到 Console。它尤其适合那些一断下来行为就变的场景——hover 效果、动画循环、resize 事件用普通断点停一下现场就变了日志断点不影响执行流日志照样出来而且可以在同一行同时打印好几个表达式。我自己的经验是排查某个字段在某一步突然变成 undefined这类问题时在赋值语句前后各放一个日志断点分别打印旧值和新值然后刷新一次页面控制台自动把几条日志按时间排好改动发生在哪一步一目了然完全不用再来回加 log、刷新、删除、再加。2.3 偏方 3debugger 关键字用对场景比任何面板都快debugger关键字是最老实的断点写在代码里执行到就停。问题是有不少人整个项目里搜不到一个debugger觉得这玩意是新手才用的真到线上想复现问题反而忘了这个最直接的入口。关键是把它用对if (condition) debugger;。业务代码里先判断条件只有命中的时候才停下这等价于一个不带 UI 的条件断点。而且debugger对 sourcemap 的支持很好构建压缩后的代码只要 devtools 能加载到 map 文件照样能停到源码对应的行。注意线上代码千万别留debugger。编译阶段最好做一层 strip 处理或者至少发布前全局搜一遍。我在实际项目中踩过某同事为了排查偶现 bug 加了条件debugger忘了删线上用户一触发条件整个页面 JS 停住白屏投诉当晚就来。3. 偏方 4-6布局和线上代码的外科手术3.1 偏方 4outline 大法一眼看穿布局样式问题最烦的不是改不回去而是看不出来哪个元素占了哪块地方。给所有元素加红色边框是最粗暴也最有效的偏方。在 Console 执行document.querySelectorAll(*).forEach(el el.style.outline 1px solid #f00);不用改任何样式文件刷新就恢复。重点在于outline不会像border那样改变盒模型尺寸加了之后布局不会乱这是它比其他高亮方式更适合排查布局的原因。用这个偏方可以快速回答三类问题某个元素到底有没有撑开它的实际尺寸是哪个父级决定的哪个透明元素挡住了点击比如弹层遮罩你觉得点了没反应一加 outline可能发现有个占满全屏的透明 div 盖在上面而你根本不知道它存在。3.2 偏方 5Overrides 本地覆写线上代码当本地改线上 CSS 或 JS 有问题传统做法是开本地服务复现或者改完重新部署一次验证要等几分钟。DevTools 的 OverridesSources Overrides能让你把线上的某个文件下载到本地目录然后直接在这个本地副本上修改刷新页面后浏览器会自动用本地文件替代网络文件。操作很简单先在 Sources 面板找到要改的文件右键选择 Save for overrides然后直接编辑保存或者把整个项目的静态文件拖进 Overrides 目录。之后你会发现 Network 面板里那些被覆盖的文件状态变成了黄色的小圆点代表本地生效。我用它处理过很多线上小 bug测试环境又复现不出来的问题直接改线上 CSS 的 margin刷新看到布局马上正常再把这个修改同步到代码仓库。注意 Overrides 只对匹配的 URL 生效路径大小写、查询参数都要一致否则不会命中。3.3 偏方 6Network 面板重放与改写请求接口调试时最常遇见的场景是页面操作了很多步才发出一个请求现在想单独调这个接口又不想重走一遍页面流程。Network 面板右键点击某个 XHR 请求选 Replay XHR浏览器会原样重发一次参数不变直接在 Console 或 Preview 看返回结果。更狠的是 Copy as fetch。右键请求选择 Copy Copy as fetch然后把这段代码粘到 Console 里执行当然可以改参数、改 headers。这就相当于手动模拟一次请求完全不需要写测试脚本。前端可以拿它验证自己的字段名对不对后端不在也能自己调试不用低声下气求联调。唯一的痛点是涉及登录态、token 有效期、签名算法的请求重放时如果 token 已过期或者签名已失效会被服务端直接拒掉。这种情况老老实实走页面流程或者用下一个偏方 Hook 网络收集真实请求。4. 偏方 7-9让看不见的数据变化现形4.1 偏方 7Hook fetch/XHR监控所有请求项目里用了第三方库你想知道它到底往哪个接口发了什么参数但不想去读它的源码。Hook 掉window.fetch是最快的办法给 fetch 包一层先记录日志再走原逻辑const origFetch window.fetch; window.fetch function(...args) { console.log([fetch], args[0], args[1]); return origFetch.apply(this, args); };在 Console 里执行这一小段之后页面里所有的 fetch 请求都会先打日志。XHR 同理包一层XMLHttpRequest.prototype.open和send。这个偏方对三类问题特别有效一是某个请求到底发没发、什么时候发的二是请求参数是不是被某个中间层改过三是第三方 SDK 偷偷调用了哪些接口。注意hook 完一定记得刷新页面移除或者至少把 console 清一下。hook 全局函数会影响所有请求如果忘了删后面每次调试都会被日志淹没还会干扰看起来无关的功能。4.2 偏方 8Object.defineProperty/Proxy捕获状态变更你有一个全局状态对象怀疑某个属性在某个时刻被改成了错误的值但找不到改它的地方。用defineProperty给属性包一层 setter在值变化的瞬间打日志并打印调用栈// 前提对象的 key 属性可配置 let val obj.key; Object.defineProperty(obj, key, { configurable: true, get() { return val; }, set(v) { console.log(key changed to, v); console.trace(); val v; } });之后每次obj.key被赋值控制台都会自动打出新值和它被修改时的调用栈顺着栈往上找凶手就跑不了。如果对象里多个属性都要监听用 Proxy 直接代理整个对象const observed new Proxy(obj, { set(target, prop, value) { console.log(${prop} changed to, value); console.trace(); return Reflect.set(target, prop, value); } });注意 Proxy 的性能开销比defineProperty大调试完要移除。这个偏方配合框架状态排查特别顺你怀疑某个 state 变了导致界面异常直接在 setter 里打栈能定位到是哪个组件、哪个事件回调改的。我自己处理过一个表格数据被神秘重置的 bug就是靠defineProperty抓到是路由守卫里做了初始化。4.3 偏方 9用 DOM 断点和 MutationObserver 锁定 DOM 篡改者样式突然不对、某个节点被删、class 被莫名追加用 console 根本看不出是谁干的。最直接的偏方其实是 Elements 面板自带的 DOM 断点右键某个节点Break on - attribute modifications 或 subtree modifications。这样一旦这个节点的属性或子节点发生变化浏览器会立刻断到实际触发修改的代码行上。如果不想断点打断流程想先记录一段时间的变化再分析那就用 MutationObserverconst observer new MutationObserver(function(mutations) { mutations.forEach(m { console.log(m.type, m.target); }); }); observer.observe(document.body, { childList: true, subtree: true, attributes: true });注意两点一是 callback 里不要再改 DOM否则 observer 自己触发自己形成死循环二是 attributes 监听可以细分attributeFilter只监听你怀疑的 class 或 style 属性减少噪音。这个偏方是纯前端反查某个全局插件改了我的 DOM的可靠手段。我遇到过一个图表库初始化后偷偷给容器加了position: fixed样式全乱最后用 MutationObserver 记录变化时间点再配合 Performance 面板回看那段 JS 执行才抓到真凶。5. 常见问题与排查技巧5.1 调试代码干干净净才算完所有偏方用完第一步就是把调试代码删干净包括hook 过的全局函数、console 日志、debugger、临时的 Observer。这些代码一旦留在生产环境轻则刷屏重则影响业务。更稳妥的做法是从根上不留在代码里debugger 交给构建插件过滤console.log 交给 ESLint 的no-console规则警告Observer 和 hook 都在 devtools 里临时执行刷新页面就没了。另一个容易漏的坑是 sourcemap。生产环境如果不打算暴露源码就不要把 map 文件传到线上否则你在控制台里定位到的源码和线上真实逻辑很可能对不上反而误导排查。正常情况下线上报错日志里有堆栈就够了不需要 map 文件常驻线上。5.2 问题排查速查表症状优先使用的偏方备选方案某个值变为 undefined 且不知道在哪改条件断点/日志断点2.2defineProperty 监控 setter4.2样式错乱但找不到哪个元素越界outline 大法3.1Elements 面板直接看 computed 样式线上小 bug 但本地复现不了Overrides 直接改线上文件3.2本地起服务 代理想单独重发某个接口请求Network 面板 Replay XHR3.3Copy as fetch 改参数手动发第三方库调用了哪些接口Hook fetch/XHR4.1Network 面板直接看请求列表DOM 被神秘修改DOM 断点 break on modifications4.3MutationObserver 记录变化页面卡顿找不到源头Performance 面板录制performance.mark/measure 打点性能和卡顿排查我在表里带了一句但值得再多说两句打开 Performance 面板录制几秒看 Long Tasks 和 FPS 掉线位置基本能锁定是渲染频繁还是 JS 执行过长这其实是所有偏方里最接近专业仪器的能力遇到性能问题建议优先开它而不是凭感觉加缓存。最后说一个我个人的习惯调试不是查完就完而是查一次沉淀一次。每个偏方我都会记在笔记里附上当时的现场截图和调用栈下次遇到同类问题直接翻。这九个偏方看着零散本质上是一套方法论——从日志、断点、请求、DOM、状态、性能这六个维度给自己装摄像头前端 bug 再刁钻也只是时间问题。
返回列表