转载信息
- 作者:Alan Norbauer
- 原文链接:67 Weird Debugging Tricks Your Browser Doesn't Want You to Know
- 翻译链接:ssshooter.com/2024-08-21-...
高级条件断点
通过在一些意想不到的地方巧妙地运用带有副作用的表达式,我们可以让条件断点等基本功能发挥出更大的作用。
日志点 / 跟踪点
例如,我们可以在断点中使用 console.log
来输出日志。日志点是一种不会暂停程序执行的断点,它会将信息记录到控制台中。虽然 Microsoft Edge 已经内置了日志点功能,Chrome 也在 v73 版本中添加了该功能,但 Firefox 尚未原生支持。不过,我们可以利用条件断点在任何浏览器中模拟日志点的行为。
如果你还希望统计某行代码被执行的次数,可以使用 console.count
来代替 console.log
。
更新(2020 年 5 月):现在所有主流浏览器都已经直接支持日志点/跟踪点功能(Chrome 日志点文档,Edge 跟踪点文档,Firefox 日志点文档)。
监视窗格
你还可以在监视窗格中使用 console.log
。例如,如果你想在每次调试器暂停时都输出 localStorage
的快照,可以创建一个 console.table(localStorage)
监视表达式:
或者,你可以在 DOM 发生变动后执行某个表达式,只需设置一个 DOM 变动断点(在元素检查器中):
然后添加你的监视表达式,例如记录 DOM 的快照:(window.doms = window.doms || []).push(document.documentElement.outerHTML)
。这样,每次任何 DOM 子树发生修改后,调试器都会暂停执行,并且新的 DOM 快照会被添加到 window.doms
数组的末尾。(请注意,目前无法创建不暂停执行的 DOM 变动断点。)
跟踪调用栈
假设你有一个用于显示加载动画的函数,以及一个用于隐藏加载动画的函数。但由于某些原因,你在代码的某个地方调用了显示函数,却没有相应地调用隐藏函数。如何找到这个未配对的显示函数调用源头呢?可以在显示函数中使用条件断点,并在其中执行 console.trace
。运行代码后,找到显示函数的最后一个堆栈跟踪,然后点击调用者,即可跳转到相应的代码位置:
动态修改程序行为
通过使用能够对程序行为产生副作用的表达式,我们可以在浏览器中动态地改变程序的运行方式。
例如,你可以覆盖 getPerson
函数的参数 id
。由于表达式 id=1
的计算结果为 true,这个条件断点会暂停调试器。为了避免这种情况,可以在表达式后面加上 , false
。
快速简易的性能分析
虽然不应该将条件断点评估时间与专业的性能分析工具相混淆,但如果你想快速测量某些操作的运行时间,可以在条件断点中使用控制台提供的计时 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 值暂停。例如,仅当文档主体的背景色为红色时暂停执行:window.getComputedStyle(document.body).backgroundColor === "rgb(255,0,0)"
仅在偶数次调用时暂停
仅在代码行每次偶数次执行时暂停:window.counter = (window.counter || 0) + 1, window.counter % 2 === 0
抽样中断
仅在代码行执行的随机样本中暂停。例如,仅在每 10 次执行中暂停 1 次:Math.random() < 0.1
永不在此暂停
在 Chrome 浏览器中,当你右键点击边栏并选择"永不在此暂停"时,Chrome 会创建一个条件为 false
的断点,因此调试器永远不会在此行暂停。
这在你想要从 XHR 断点中排除某行代码,或者忽略特定异常时非常有用。
自动生成实例 ID
通过在构造函数中设置此条件断点,可以自动为每个类的实例分配一个唯一的 ID:(window.instances = window.instances || []).push(this)
然后,你可以通过 window.instances.indexOf(instance)
来检索该唯一 ID(例如,在类方法中使用 window.instances.indexOf(this)
)。
程序化切换
使用全局布尔值来控制一个或多个条件断点:
然后,你可以通过编程方式来切换这个布尔值,例如:
-
手动从控制台:
iniwindow.enableBreakpoints = true;
-
从其他断点:
-
从控制台上的定时器:
inisetTimeout(() => (window.enableBreakpoints = true), 5000);
-
等等。
监视类方法调用
你可以使用 Chrome 浏览器的 monitor
命令行方法来轻松跟踪对类方法的所有调用。例如,假设你有一个 Dog
类:
javascript
class Dog {
bark(count) {
/* ... */
}
}
如果你想知道所有 Dog
实例的 bark
方法被调用的情况,可以将以下代码粘贴到命令行中:
css
var p = Dog.prototype;
Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
你将在控制台中看到如下输出:
sql
> function bark called with arguments: 2
如果你想在任何方法调用时暂停执行(而不仅仅是记录到控制台),可以使用 debug
命令代替 monitor
。
从特定实例
如果你不知道类,但有一个实例:
css
var p = instance.constructor.prototype;
Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
这在你想要为任何类的任何实例编写一个函数时非常有用(不仅仅是 Dog
类)。
调用和调试函数
在控制台中调用你想要调试的函数之前,先调用 debugger
语句。例如,假设有以下函数:
csharp
function fn() {
/* ... */
}
在控制台中输入:
php
> debugger; fn(1);
然后,通过"进入下一个函数调用"来调试 fn
函数的实现。
这在你不想找到 fn
的定义并手动添加断点,或者 fn
动态绑定到一个函数且你不知道源代码在哪里时非常有用。
在 Chrome 浏览器中,你还可以选择在命令行中调用 debug(fn)
,这样调试器会在每次调用 fn
时暂停执行。
在 URL 更改时暂停执行
在单页应用程序修改 URL 之前暂停执行(即在某些路由事件发生时):
ini
const dbg = () => {
debugger;
};
history.pushState = dbg;
history.replaceState = dbg;
window.onhashchange = dbg;
window.onpopstate = dbg;
创建一个不会破坏导航的 dbg
版本,并使其能够暂停执行,这可以作为一个练习留给读者。
此外,请注意,以上方法无法处理代码直接调用 window.location.replace/assign
的情况,因为在赋值后页面会立即卸载,因此没有太多可供调试的内容。如果你仍然想查看这些重定向的源代码(并调试重定向发生时的状态),可以在 Chrome 浏览器中使用 debug
命令来调试相关方法:
scss
debug(window.location.replace);
debug(window.location.assign);
调试属性读取
如果你有一个对象,并且想知道何时读取了它的某个属性,可以使用带有 debugger
调用的对象 getter。例如,将 {configOption: true}
转换为 {get configOption() { debugger; return true; }}
(可以在原始源代码中修改,也可以使用条件断点)。
这在你传递一些配置选项并想查看它们是如何被使用时非常有用。
使用 copy()
你可以使用 copy()
控制台 API 将有用的信息直接从浏览器复制到剪贴板,而不会截断字符串。以下是一些你可能想要复制的内容:
- 当前 DOM 的快照:
copy(document.documentElement.outerHTML)
- 资源的元数据(例如图像):
copy(performance.getEntriesByType("resource"))
- 一个大的 JSON blob,并进行格式化:
copy(JSON.parse(blob))
- 转储你的 localStorage:
copy(localStorage)
- 等等。
调试 HTML/CSS
JS 控制台在诊断 HTML/CSS 问题时非常有用。
在禁用 JS 的情况下检查 DOM
在 DOM 检查器中,按下 Ctrl +
(Chrome/Windows)可以随时暂停 JavaScript 的执行。这允许你在不担心 JavaScript 修改 DOM 或事件(例如鼠标悬停)导致 DOM 发生变化的情况下,检查 DOM 的快照。
检查难以捕捉的元素
假设你想检查一个只在特定条件下才会出现的 DOM 元素。检查该元素需要将鼠标移动到它上面,但是当你尝试这样做时,它却消失了:
要检查这个元素,你可以将以下代码粘贴到控制台中:setTimeout(function() { debugger; }, 5000);
。这会给你 5 秒钟的时间来触发 UI,然后一旦 5 秒的定时器到期,JavaScript 的执行将会暂停,这样任何事情都不会使你的元素消失。你可以自由地将鼠标移动到开发者工具上,而不会丢失该元素:
在 JavaScript 执行暂停时,你可以检查元素,编辑它的 CSS,在 JS 控制台中执行命令等等。
这在检查依赖于特定光标位置、焦点等状态的 DOM 元素时非常有用。
记录 DOM 快照
要获取当前状态下 DOM 的副本,可以使用:
scss
copy(document.documentElement.outerHTML);
要每秒记录一次 DOM 的快照,可以使用:
ini
doms = [];
setInterval(() => {
const domStr = document.documentElement.outerHTML;
doms.push(domStr);
}, 1000);
或者,只是将 DOM 快照输出到控制台:
javascript
setInterval(() => {
const domStr = document.documentElement.outerHTML;
console.log("snapshotting DOM: ", domStr);
}, 1000);
监视聚焦元素
javascript
(function () {
let last = document.activeElement;
setInterval(() => {
if (document.activeElement !== last) {
last = document.activeElement;
console.log("Focus changed to: ", last);
}
}, 100);
})();
查找加粗元素
ini
const isBold = (e) => {
let w = window.getComputedStyle(e).fontWeight;
return w === "bold" || w === "700";
};
Array.from(document.querySelectorAll("*")).filter(isBold);
仅查找后代元素
或者,仅检查当前在元素检查器中选择的元素的后代元素:
javascript
Array.from($0.querySelectorAll("*")).filter(isBold);
引用当前选择的元素
控制台中的 $0
是对元素检查器中当前所选元素的自动引用。
引用之前的元素
在 Chrome 和 Edge 浏览器中,你可以使用 $1
访问你上次检查的元素,使用 $2
访问更早之前的元素,依此类推。
获取事件监听器
在 Chrome 浏览器中,你可以检查当前所选元素的事件监听器:getEventListeners($0)
,例如:
监视元素的事件
调试所选元素的所有事件:monitorEvents($0)
调试所选元素的特定事件:monitorEvents($0, ["control", "key"])