[译] 浏览器不想让你知道的67个怪异调试技巧
原文地址:https://alan.norbauer.com/articles/browser-debugging-tricks
一系列实用而不显眼的小技巧,让你充分利用浏览器的调试器。文章预设你对浏览器的开发者工具有中级或以上的了解。
进阶的条件断点
通过在你意想不到的地方使用具有副作用的表达式,我们可以从条件断点等基本功能中获取更多的功能。
日志点和追踪点
例如,我们可以在断点中使用 console.log
。日志点 logpoints
是在不暂停执行的情况下将日志记录到控制台的断点。Microsoft Edge 内置日志点已有一段时间,而 Chrome 浏览器也在 v73
中添加了日志点,但 Firefox 浏览器没有。不过,我们可以在任何浏览器中使用条件断点来模拟日志点。
如果你还想计算该行的执行次数,请使用 console.count
而不是 console.log
。
更新(2020 年 5 月):现在所有主流浏览器都直接支持日志点/跟踪点(Chrome 浏览器日志点、Edge 浏览器跟踪点、Firefox 浏览器日志点)。
观察窗格
你还可以在观察窗格中使用 console.log
。例如,要在调试器中每次暂停应用程序时转储 localStorage
的快照,可以创建一个 console.table(localStorage)
观察器:
或者,要在 DOM 修改后执行表达式,可以设置 DOM 修改断点(在元素检查器 Element Inspector
中):
然后添加观察表达式,例如记录 DOM 的快照:(window.doms = window.doms || []).push(document.documentElement.outerHTML)
。现在,在修改任何 DOM 子树后,调试器将暂停执行,然后新的 DOM 快照会出现在 window.doms
数组的末尾。(没有办法创建不暂停执行的 DOM 修改断点)。
追踪调用堆栈
比方说,你有一个显示加载旋转器的函数和一个隐藏它的函数,但在代码的某个地方,你调用 show 方法时没有调用匹配的 hide 方法。如何找到未配对 show 方法调用的源代码呢?在 show 方法的条件断点中使用 console.trace
,运行代码,找到 show 方法的最后一次堆栈追踪,然后点击调用者 caller
进入代码:
改变程序行为
通过使用对程序行为有副作用的表达式,我们可以在浏览器中即时更改程序行为。
例如,你可以覆盖 getPerson
函数的参数 id
。 由于 id=1
的值为 true,这个条件断点会暂停调试器。为避免这种情况,请在表达式中添加 ,false
。
快速、粗略的性能分析
你不应该用条件断点评估时间之类的东西来混淆性能分析,但如果你想快速而又粗略地测量某个程序运行所需的时间,可以在条件断点中使用控制台 timing API。在起点设置一个条件断点 console.time('label')
,然后在终点设置一个条件断点 console.timeEnd('label')
。每当要测量的程序运行时,浏览器都会向控制台记录运行时间。
使用函数的参数
参数数量匹配时中断
仅在当前函数被以 3 个参数调用时暂停:arguments.callee.length === 3
。
当重载的函数有可选参数时会非常有用。
函数参数不匹配时中断
仅在调用当前函数时使用了错误的参数数量时暂停:(arguments.callee.length) != arguments.length
。
在查找函数调用处的错误时会非常有用。
使用时间
跳过页面加载
页面加载 5 秒后才暂停:performance.now() > 5000
。
当你想设置断点,但只想在初始的页面加载完成之后才暂停执行,此功能会非常有用。
跳过 N 秒
如果在接下来的 5 秒内遇到断点,则不暂停执行,而是在之后的任何时间暂停: window.baseline = window.baseline || Date.now(), (Date.now() - window.baseline) > 5000
。
如果想随时在控制台中重置计数器,你只需要执行:window.baseline = Date.now()
。
使用 CSS
根据计算的 CSS 值暂停执行,例如,仅在 document body 的背景色为红色时暂停执行:window.getComputedStyle(document.body).backgroundColor === "rgb(255,0,0)"
。
只在偶数次调用
间隔一行执行时,才暂停一次: window.counter = window.counter || 0, window.counter % 2 === 0
。
样本中断
仅在随机抽样执行该行时中断,例如,仅在每执行 10 次时中断 1 次:Math.random() < 0.1
。
Never Pause Here
右键单击 gutter 并选择 “Never Pause Here” 时,Chrome 浏览器会创建一个条件断点,该断点为 false
且永不通过。这样,调试器就不会在这一行暂停。
当你想将某一行从 XHR 断点中排除、忽略正在抛出的异常等时,它会非常有用。
自动的实例 IDs
通过在构造函数中设置以下条件断点,自动为类的每个实例分配唯一 ID:(window.instances = window.instances || []).push(this)
。
然后检索唯一 ID:window.instances.indexOf(instance)
(例如,在类方法中使用 window.instances.indexOf(this)
)。
程序切换
使用全局布尔值选取一个或多个条件断点:
然后通过编程切换布尔值,例如:
手动从控制台中:
jswindow.enableBreakpoints = true;
从其他断点:
从控制台的 timer 中:
jssetTimeout(() => (window.enableBreakpoints = true), 5000);
等等
monitor()
类调用
WARNING
只适用于 Chrome 浏览器
你可以使用 Chrome 浏览器的 monitor
命令行方法,轻松跟踪对类方法的所有调用。例如,给定一个类 Dog
:
class Dog {
bark(count) {
/* ... */
}
}
如果我们想知道对 Dog
所有实例的调用,请将此内容粘贴到命令行中:
var p = Dog.prototype;
Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
就会在控制台中得到输出结果:
> function bark called with arguments: 2
如果你想暂停任意方法调用的执行(而不只是记录到控制台),可以使用 debug
代替 monitor
。
源自指定的实例
如果你不知道所属的类,但有一个实例:
var p = instance.constructor.prototype;
Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
当你想编写一个函数,对任何类的任何实例(而不仅仅是 Dog
)执行此操作时,该函数就会非常有用。
调用和调试函数
在调用要在控制台中调试的函数之前,先调用 debugger
。例如:
function fn() {
/* ... */
}
来自你的浏览器控制台:
> debugger; fn(1);
然后选择 Step into next function call
,进而调试 fn
的执行流程。
当你不想查找 fn
的定义并手动添加断点时,或者当 fn
动态绑定到一个函数上,而你不知道源代码在哪里时,这种方法会非常有用。
在 Chrome 浏览器中,你还可以选择在命令行中调用 debug(fn)
,这样每次调用 fn
时,调试器都会暂停执行 fn
。
URL 变更时暂停执行
在单页面应用程序修改 URL(即发生路由事件)之前暂停执行:
const dbg = () => {
debugger;
};
history.pushState = dbg;
history.replaceState = dbg;
window.onhashchange = dbg;
window.onpopstate = dbg;
至于如何创建一个能在不中断导航的情况下暂停执行的 dbg
代码版本,读者可以自行决定如何实现。
此外,请注意这并不能处理代码直接调用 window.location.replace/assign
的情况,因为页面会在方法执行后立即卸载,所以没有什么可调试的。如果还想查看这些重定向的来源(并调试重定向时的状态),在 Chrome 浏览器中可以 debug
相关方法:
debug(window.location.replace);
debug(window.location.assign);
调试属性读取
如果你有一个对象,并想知道它的某个属性何时被读取,可以使用带有 debugger
调用的对象 getter。例如,将 { configOption: true }
转换为 {get configOption() { debugger; return true; }}
(在原始源代码中或使用条件断点)。
当你向某个程序传递一些配置选项,并希望查看这些选项的使用情况时,这种方法非常有用。
使用 copy()
WARNING
只适用于 Chrome/Firefox 浏览器
使用 copy()
控制台 API,你可以将浏览器中的有趣信息直接复制到剪贴板,而无需截断任何字符串。你可能想复制一些有趣的内容:
- 当前 DOM 的快照:
copy(document.documentElement.outerHTML)
- 资源(如图片)的元数据:
copy(performance.getEntriesByType("resource"))
- 格式化后的大型 JSON blob:
copy(JSON.parse(blob))
- 本地存储的转储:
copy(localStorage)
- 等等。
调试 HTML/CSS
JS 控制台有助于诊断 HTML/CSS 的问题。
禁用 JS 检查 DOM
在 DOM 检查器中,按下 ctrl+\
(Chrome/Windows)键可随时暂停 JS 的执行。这样,你就可以检查 DOM 的快照,而不必担心 JS 会改变 DOM 或事件(如 mouseover
)会导致 DOM 在你眼皮底下发生变化的情况发生。
检查难以捉摸的元素
假设你要检查一个 DOM 元素,而该元素只在特定条件下才会出现。检查该元素需要将鼠标移动到该元素上,但当你尝试移动时,该元素却消失了:
要检查元素,可以将以下内容粘贴到控制台:setTimeout(function() { debugger; }, 5000);
。这样就有 5 秒钟的时间来触发 UI,而一旦 5 秒钟的计时器到了,JS 的执行就会暂停,元素也不会消失。你就可以随意地将鼠标移到开发者工具上,而不会丢失元素:
在暂停执行 JS 时,你可以检查元素、编辑 CSS、在 JS 控制台中执行命令等。
在检查依赖于特定光标位置、焦点等的 DOM 时非常有用。
记录 DOM 快照
抓取当前状态下的 DOM 副本:
copy(document.documentElement.outerHTML);
每秒记录一次 DOM 的快照:
doms = [];
setInterval(() => {
const domStr = document.documentElement.outerHTML;
doms.push(domStr);
}, 1000);
或者直接输出到控制台:
setInterval(() => {
const domStr = document.documentElement.outerHTML;
console.log("snapshotting DOM: ", domStr);
}, 1000);
监控聚焦元素
(function () {
let last = document.activeElement;
setInterval(() => {
if (document.activeElement !== last) {
last = document.activeElement;
console.log("Focus changed to: ", last);
}
}, 100);
})();
找到加粗元素
const isBold = (e) => {
let w = window.getComputedStyle(e).fontWeight;
return w === "bold" || w === "700";
};
Array.from(document.querySelectorAll("*")).filter(isBold);
只找后代
或者只是检查器中当前所选元素的后代:
Array.from($0.querySelectorAll("*")).filter(isBold);
引用当前选择元素
控制台中的 $0
是对元素检查器中当前选定元素的自动引用。
先前检查过的元素
WARNING
只适用于 Chrome/Edge 浏览器
在 Chrome 浏览器和 Edge 中,你可以用 $1
访问上次检查过的元素,用 $2
访问之前的元素,等等。
获取事件监听器
WARNING
只适用于 Chrome 浏览器
在 Chrome 浏览器中,你可以通过 getEventListeners($0)
查看当前选中元素的事件监听器,例如:
监控元素的事件
WARNING
只适用于 Chrome 浏览器
调试选定元素的所有事件: monitorEvents($0)
调试选定元素的特定事件:monitorEvents($0, ["control", "key"])