一份有用的、但是不那么明显的技巧列表,帮助我们充分利用浏览器的调试器。
TIP
通过在意想不到的地方使用具有副作用的表达式,我们可以从像条件断点这样的基本功能中榨取更多的功能。
例如,我们可以在断点处使用 console.log
。日志点是记录到控制台的断点,但不会暂停执行。虽然 Microsoft Edge 已经内置了日志点一段时间,Chrome 在 v73 版本中刚刚添加了它们,但 Firefox 没有。不过,我们可以使用条件断点在任何浏览器中模拟它们。
使用 console.count
而不是 console.log
,如果我们还想要一个运行计数,以了解该行代码执行了多少次。
在2020年5月之后所有主流浏览器都直接支持日志点/跟踪点(Chrome 日志点,Edge 跟踪点,Firefox 日志点)。
我们还可以在使用控制台的监视面板。例如,每次我们的应用程序在调试器中暂停时,为了转储 localStorage
的一个快照,我们可以创建一个 console.table(localStorage)
监视器:
或者要在 DOM 变更后执行一个表达式,可以设置一个 DOM 变更断点(在 Element Inspector 中):
然后添加我们的监视表达式,例如记录DOM的快照:(window.doms = window.doms || []).push(document.documentElement.outerHTML)
。现在,经过任何 DOM 子树的修改,调试器将暂停执行,新的 DOM 快照将位于 window.doms
数组的末尾。(没有办法创建一个不暂停执行的DOM突变断点。)
假设我们有一个函数显示加载旋转器,还有一个函数隐藏它,但在我们的代码中,我们调用了显示方法,但没有匹配的隐藏调用。我们如何找到未配对显示调用的源头?在显示方法中使用 console.trace
在一个条件断点,运行我们的代码,找到显示方法的最后一个堆栈跟踪,点击调用者跳转到代码:
通过使用对程序行为有副作用的表达式,我们可以在浏览器中即时改变程序行为。
例如,我们可以覆盖传递给 getPerson
函数的参数id。由于 id=1
的计算结果为真,这个条件断点会暂停调试器。为了防止这种情况,可以在表达式后面添加 , false
。
我们不应该将性能分析与诸如条件断点评估时间之类的事情混淆,但如果我们想要快速而粗略地测量某件事情运行所需的时间,我们可以在条件断点中使用控制台计时 API 。在我们的起始点设置一个条件断点,条件为 console.time('label')
,在结束点设置一个条件断点,条件为 console.timeEnd('label')
。每次我们测量的事情运行时,浏览器都会在控制台记录它运行了多长时间。
仅当当前函数以3个参数被调用时暂停:arguments.callee.length === 3
当我们有一个有可选参数的重载函数时,这很有用。
仅当当前函数被调用时传入了错误的参数数量时暂停:(arguments.callee.length) != arguments.length
在查找函数调用点的漏洞时很有用。
页面加载后不要暂停,直到5秒后:performance.now() > 5000
当我们想设置一个断点,但只对页面初始加载后暂停执行感兴趣时很有用。
不要在接下来的5秒内如果断点被触发就暂停执行,但在5秒后任何时候都暂停:window.baseline = window.baseline || Date.now(), (Date.now() - window.baseline) > 5000
我们可以随时从控制台重置计数器:window.baseline = Date.now()
根据计算出的CSS值暂停,例如,只有在 document body 具有红色背景色时才暂停执行: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
当我们右键点击边距并选择“Never Pause Here”,Chrome 会创建一个条件断点,该断点为假且永远不会通过。这样设置后,调试器就永远不会在这一行暂停。
当我们想要免除某行的XHR断点,忽略正在抛出的异常等情况时很有用。
自动为类的每个实例分配一个唯一 ID ,通过在构造函数中设置这个条件断点:(window.instances = window.instances || []).push(this)
然后要检索这个唯一ID:window.instances.indexOf(instance)
(例如,在类方法中使用window.instances.indexOf(this)
)
使用一个全局 Boolean 来控制一个或多个条件断点:
然后通过编程方式切换 Boolean ,例如。
window.enableBreakpoints = true
setTimeout(() => (window.enableBreakpoints = true), 5000)
我们可以使用Chrome的监控命令行方法轻松追踪所有对类方法的调用。例如,给定一个类 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
时,调试器都会暂停执行。
在单页应用程序修改URL(即发生某些路由事件)之前暂停执行:
const dbg = () => {
debugger;
};
history.pushState = dbg;
history.replaceState = dbg;
window.onhashchange = dbg;
window.onpopstate = dbg;
创建一个版本的dbg,它在不中断导航的情况下暂停执行,这是一个留给读者的练习。
另外,请注意,这并不能处理当代码直接调用 window.location.replace/assign
时的情况,因为页面在赋值后会立即卸载,因此没有什么可以调试的。如果我们仍然想要查看这些重定向的来源(并在重定向时调试我们的代码状态),在 Chrome 中,我们可以调试相关的方法:
debug(window.location.replace);
debug(window.location.assign);
如果我们有一个对象,并且想要知道何时读取了它的属性,可以使用带有 debugger
调用对象的 getter 。例如,将 {configOption: true}
转换为 {get configOption() { debugger; return true; }}
(可以在原始源代码中进行,也可以使用条件断点)。
当我们向某个东西传递一些配置选项,并且想要了解它们是如何被使用的时候,这会很有用。
我们可以使用 copy()
控制台 API 直接从浏览器复制有趣的信息到我们的剪贴板,而不会出现任何字符串截断。一些我们可能会想要复制的有趣事物:
copy(document.documentElement.outerHTML)
copy(performance.getEntriesByType("resource"))
copy(JSON.parse(blob))
copy(localStorage)
JS 控制台在诊断 HTML/CSS 问题时可能会很有帮助。
在 DOM 检查器中,按下 Ctrl+\(Chrome/Windows)可以随时暂停 JS 执行。这样,我们就可以检查 DOM 的快照,而不必担心 JS 改变 DOM 或事件(例如鼠标悬停)导致 DOM 在我们检查时发生变化。
假设我们想检查一个只在特定条件下出现的DOM元素。检查这个元素需要将鼠标移动到它上面,但当我们尝试这样做时,它就会消失:
要检查元素,我们可以将此代码粘贴到我们的控制台中:setTimeout(function() { debugger; }, 5000);
。这会给我们5秒钟的时间来触发用户界面,一旦5秒计时器结束,JavaScript 的执行将暂停,没有任何东西会让我们的元素消失。我们可以自由地移动鼠标到开发者工具上,而不会丢失该元素。
当 JavaScript 执行暂停时,我们可以检查元素,编辑其CSS,在JS控制台中执行命令等。
在检查依赖于特定光标位置、焦点等的 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("快照DOM: ", domStr);
}, 1000);
监控焦点元素
(function () {
let last = document.activeElement;
setInterval(() => {
if (document.activeElement !== last) {
last = document.activeElement;
console.log("焦点已改变至: ", last);
}
}, 100);
})()
const isBold = (e) => {
let w = window.getComputedStyle(e).fontWeight;
return w === "bold" || w === "700";
};
Array.from(document.querySelectorAll("*")).filter(isBold);
或者只是当前在检查器中选择的元素的 descendants :
Array.from($0.querySelectorAll("*")).filter(isBold);
在控制台中,$0
是当前在元素检查器中选定元素的自动引用。
在 Chrome 和 Edge 浏览器中,我们可以通过 $1
访问我们最后检查的元素,通过 $2
访问之前检查的元素,以此类推。
在 Chrome 浏览器中,我们可以检查当前选中元素的事件监听器:getEventListeners($0)
,例如:
为选定元素调试所有事件:monitorEvents($0)
为选定元素调试特定事件:monitorEvents($0, ["control", "key"])