目录

浏览器 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"])

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
好_快11 分钟前
Lodash源码阅读-lastIndexOf
前端·javascript·源码阅读
好_快12 分钟前
Lodash源码阅读-equalArrays
前端·javascript·源码阅读
代码代码快快显灵13 分钟前
使用 Spring Security的一些常用功能
java·前端·spring·springsecurity
恋猫de小郭1 小时前
Flutter 新一代状态管理框架 signals ,它究竟具备什么魔法和优势
android·前端·flutter
大霸王龙2 小时前
基于HTML的邮件发送状态查询界面设计示例
前端·javascript·html
z26373056114 小时前
为什么后端路由需要携带 /api 作为前缀?前端如何设置基础路径 /api?
前端
eggcode7 小时前
CSS选择器
前端·css
老胡说前端7 小时前
css white-space: pre-line; 用处大
前端·css
还是鼠鼠7 小时前
Node.js 包与 npm 详解:使用 npm 的重要注意事项与最佳实践
前端·javascript·vscode·node.js
图扑软件8 小时前
图扑软件 2D 组态:工业组态与硬件监控的物联网赋能
javascript·人工智能·物联网·低代码·数字孪生·可视化·工业组态