67 你的浏览器不想让你知道的奇怪调试技巧

67 你的浏览器不想让你知道的奇怪调试技巧

一份实用而不显眼的黑客高手列表,让您充分利用浏览器的调试器。假设您对开发工具有中级或更高程度的了解。

高级条件断点

通过在您意想不到的地方使用会产生副作用的表达式(有预期之外的结果),我们可以从诸如条件断点等基本功能中榨取出更多的功能。

日志断点、跟踪断点

例如,我们可以在断点中使用 console.log。日志断点是指不会暂停执行并将日志记录到控制台的断点。尽管 Microsoft Edge 已经内置了日志断点有一段时间,Chrome 则是在 v73 版本中才添加了这个功能,但 Firefox 却没有。不过,我们可以使用条件断点 (conditional breakpoints) 在任何浏览器中模拟这一功能。

Conditional Breakpoint - console.log

如果您还想记录该行代码的执行次数,请使用 console.count 代替 console.log

更新(2020 年 5 月):所有主流浏览器现在都直接支持日志断点/跟踪断点 (Chrome LogpointsEdge TracepointsFirefox Logpoints)

监视窗格

您还可以在监视窗格中使用 console.log。例如,要在调试器中每次暂停应用程序时转储 localStorage 的快照,可以创建一个 console.table(localStorage) watch Pane:

console.table in watch pane

或者,要在 DOM 突变后执行表达式,请设置 DOM 变化断点(在元素检查器 Element Inspector 中):

DOM Mutation Breakpoint

然后添加观察表达式 (watch expression),例如记录 DOM 的快照:(window.doms = window.doms || []).push(document.documentElement.outerHTML)。现在,在修改任何 DOM 子树之后,调试器将暂停执行,新的 DOM 快照将位于 window.doms 数组的最后一个。(没有办法创建不暂停执行的 DOM 突变断点)。

跟踪调用堆栈

举个例子,你有一个 showSpinner 的函数和一个 hideSpinner 的函数,但在代码的某个地方,你调用 show 方法时没有调用匹配的 hide 方法。如何找到未配对显示调用的来源?在 show 方法的条件断点中使用 console.trace,运行代码,找到 show 方法的最后一次堆栈跟踪,然后点击调用者进入代码:

console.trace in conditional breakpoint

改变程序行为

通过使用对程序行为有副作用的表达式,我们可以在浏览器中即时更改程序行为。

例如,你可以覆盖 getPerson 函数的参数 id。由于 id=1 的值为 true,这个条件断点会暂停调试器。为避免这种情况,可在表达式中添加 , false

Conditional Breakpoint - parameter override

快速、简单的性能分析

您不应该用条件断点 (conditional breakpoint) 评估时间之类的东西来性能分析,但如果您想快速而又肮脏地测量某个程序运行所需的时间,可以在条件断点中使用控制台计时 API。在起点设置一个条件为 console.time('label') 的断点,在终点设置一个条件为 console.timeEnd('label') 的断点。每次运行要测量的内容时,浏览器都会在控制台中记录运行时间。

Conditional Breakpoint - performance profile

利用函数的参数个数

按参数个数中断

只有在当前函数被调用并包含 3 个参数时才暂停:arguments.callee.length === 3

在重载函数 (overloaded function) 有可选参数时非常有用。

Conditional Breakpoint - argument length

在函数参数个数不匹配时设置断点

仅在调用当前函数时使用了错误的参数数时暂停:(arguments.callee.length) != arguments.length

Conditional Breakpoint - arity check

在查找函数调用站点中的错误时非常有用。

运行时间

跳过页面加载

页面加载 5 秒后才暂停:performance.now() > 5000

当您想设置断点,但只想在初始页面加载后暂停执行时很有用。

跳过 N 秒

如果在接下来的 5 秒钟内遇到断点,则不暂停执行,但随时可以在之后暂停:window.baseline = window.baseline || Date.now(), (Date.now() - window.baseline) > 5000

可以随时从控制台 (console) 重置计数器:window.baseline = Date.now()

Using CSS

根据计算的 CSS 值暂停执行,例如,仅在文档正文背景颜色为红色时暂停执行:window.getComputedStyle(document.body).backgroundColor === "rgb(255,0,0)"

仅限偶数次调用

每执行一次后暂停一次:window.counter = window.counter || 0, window.counter % 2 === 0

根据采样暂停

仅在随机抽样执行该行时暂停,例如,仅在每执行 10 次该行时暂停 1 次:Math.random() < 0.1

此处永不暂停

Chrome Edge Firefox Safari

右键单击边栏位置并选择 Never Pause Here(此处永不暂停),Chrome 浏览器会创建一个条件断点,false,永远不会触发。这样调试器就不会在这一行暂停。

Never Pause Here

Never Pause Here Result

当你想将某一行从 XHR 断点中排除、忽略正在抛出的异常等时,它就会派上用场。

自动分配实例 ID

通过在构造函数中设置以下条件断点,自动为类的每个实例分配唯一 ID:(window.instances = window.instances || []).push(this)

然后检索唯一 ID:window.instances.indexOf(instance) (例如,在类方法中使用 window.instances.indexOf(this))

编程触发

使用全局布尔值选取一个或多个条件断点:

Boolean gate

然后通过编程切换布尔值,例如

  • 从控制台手动输入

    window.enableBreakpoints = true;
  • 从其他断点 Boolean gate - enable from other breakpoint

  • 从控制台上的计时器

    setTimeout(() => (window.enableBreakpoints = true), 5000);
  • 等等

监视类 (class) 的调用

Chrome Edge Firefox Safari

您可以使用 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

从特定实例

Chrome Edge Firefox Safari

如果您不知道该类,但有一个实例:

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 改变时暂停执行

在单页面应用程序修改 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 调用的对象获取器。例如,将 {configOption: true} 转换为 {get configOption() { debugger; return true; }} (在原始源代码中或使用条件断点)。

当你向某个程序传递一些配置选项,并希望查看这些选项的使用情况时,它就会派上用场。

使用 copy()

Chrome Edge Firefox Safari

您可以使用 copy() 控制台 API 将浏览器中有趣的信息直接复制到剪贴板,而无需截断任何字符串。您可能想复制一些有趣的内容:

  • 当前 DOM 的快照:copy(document.documentElement.outerHTML)
  • 资源的元数据(如图像):copy(performance.getEntriesByType("resource"))
  • 格式化后的大型 JSON blob:copy(JSON.parse(blob))
  • 本地存储的转储:copy(localStorage)
  • 等等。

调试 HTML/CSS

JS 控制台有助于诊断 HTML/CSS 的问题。

在禁用 JavaScript 的情况下检查 DOM

在 DOM 检查器中按下 ctrl + \ (Chrome/Windows) 可以随时暂停 JS 的执行。这样您就可以检查 DOM 的快照,而不必担心 JS 会改变 DOM 或事件(如鼠标悬停)会导致 DOM 从您脚下发生变化。

检查一个难以捉摸的元素

假设您要检查一个 DOM 元素,而该元素只有在有条件的情况下才会出现。检查该元素需要将鼠标移动到该元素上,但当您尝试移动时,该元素却消失了:

Elusive element

为了检查元素,您可以将以下内容粘贴到控制台中:setTimeout(function () {debugger; }, 5000);。这将为您提供 5 秒钟的时间来触发用户界面,一旦 5 秒计时器计时结束,JS 的执行就会暂停,元素也不会消失。您可以自由地将鼠标移到开发工具上,而不会丢失元素:

Elusive element - inspected

在暂停执行 JS 时,您可以检查元素、编辑 CSS、在 JS 控制台中执行命令等。

在检查依赖于特定光标位置、焦点等的 DOM 时非常有用。

记录 DOM 的快照

复制当前状态下的 DOM:

copy(document.documentElement.outerHTML);

每秒记录一次 DOM 的快照:

doms = [];
setInterval(() => {
  const domStr = document.documentElement.outerHTML;
  doms.push(domStr);
}, 1000);

或者直接转存 (dump) 到控制台:

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);
})();

Monitor focused element

查找加粗元素

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 是对元素检查器中当前选定元素的自动引用 (automatic reference)。

前面的元素

Chrome Edge Firefox Safari

在 Chrome 和 Edge 中,你可以使用 $1 访问你上次检查的元素,使用 $2 访问上上次检查的元素,以此类推。

查看事件监听器

Chrome Edge Firefox Safari

在 Chrome 浏览器中,您可以使用 getEventListeners($0) 检查当前选定元素的事件监听器,例如

getEventListeners

监视元素的事件

Chrome Edge Firefox Safari

调试选定元素的所有事件:monitorEvents($0)

调试选定元素的特定事件:monitorEvents($0, ["control", "key"])

monitorEvents

脚注

  1. 除非浏览器标识另有说明,否则 Chrome、Firefox 和 Edge 浏览器均支持提示功能:
Chrome Edge Firefox Safari

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×