浏览器 67 个实用 Debug 技巧

转载信息

高级条件断点

通过在一些意想不到的地方巧妙地运用带有副作用的表达式,我们可以让条件断点等基本功能发挥出更大的作用。

日志点 / 跟踪点

例如,我们可以在断点中使用 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))。

程序化切换

使用全局布尔值来控制一个或多个条件断点:

然后,你可以通过编程方式来切换这个布尔值,例如:

  • 手动从控制台:

    ini 复制代码
    window.enableBreakpoints = true;
  • 从其他断点:

  • 从控制台上的定时器:

    ini 复制代码
    setTimeout(() => (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"])

相关推荐
工藤学编程11 分钟前
零基础学AI大模型之CoT思维链和ReAct推理行动
前端·人工智能·react.js
徐同保12 分钟前
上传文件,在前端用 pdf.js 提取 上传的pdf文件中的图片
前端·javascript·pdf
怕浪猫13 分钟前
React从入门到出门第四章 组件通讯与全局状态管理
前端·javascript·react.js
博主花神13 分钟前
【React】扩展知识点
javascript·react.js·ecmascript
欧阳天风20 分钟前
用setTimeout代替setInterval
开发语言·前端·javascript
EndingCoder24 分钟前
箭头函数和 this 绑定
linux·前端·javascript·typescript
郑州光合科技余经理24 分钟前
架构解析:同城本地生活服务o2o平台海外版
大数据·开发语言·前端·人工智能·架构·php·生活
沐墨染26 分钟前
大型数据分析组件前端实践:多维度检索与实时交互设计
前端·elementui·数据挖掘·数据分析·vue·交互
xkxnq30 分钟前
第一阶段:Vue 基础入门(第 11 天)
前端·javascript·vue.js
lifejump30 分钟前
Pikachu | Unsafe Filedownload
前端·web安全·网络安全·安全性测试