• 如何调试前端代码?
  • 发布于 2个月前
  • 107 热度
    0 评论
今天给大家带来一些调试技巧。

条件断点:
条件断点是一种高级的调试技巧,它允许我们为某个特定的代码行设置断点,但这个断点只有在满足某个特定条件时才会触发。我们可以在想要调试的地方右键,选择 Add conditional breakpoint

然后在条件中输入断点的生效条件,例如我们可以让它在这个位置只打印日志不进行暂停:

还有一些你可能会使用到的调试条件:
仅在使用 2 个参数调用当前函数时断点:arguments.callee.length === 2 ,如果这个函数有多个可选的重载的时候会很有用。
页面加载后 7 秒才断点:performance.now() > 7000,当你想要设置断点,但只想在初始页面加载后断点执行时会很有用。

同理,我们还可以实现更精准一点的时间控制:如果在接下来的 7 秒内命中断点,则不要断点执行,而是在之后随时断点:
// 堆代码 duidaima.com
window.baseline = window.baseline || 
Date.now(), (Date.now() - window.baseline) > 7000
也可以根据计算的 CSS 值断点,例如,仅当文档正文具有红色背景色时才暂停执行:
window.getComputedStyle(document.body).backgroundColor === "rgb(255,0,0)"
我们还可以用它来追踪函数的调用堆栈:比如说,你有一个函数用于显示加载动画,还有一个函数用于隐藏它,但在你的代码中的某个地方,你调用了展示动画的方法,但没有相应的隐藏动画的调用。你要如何找到这个没有配对的展示动画方法的调用源头呢?你可以在展示动画方法的条件断点中使用 console.trace 来运行代码,找到对应展示动画方法的最后一个栈追踪,点击调用源就可以跳转到对应的代码位置:

甚至我们还可以利用条件断点来帮助我们对函数进行性能分析,我们只需要在函数前后插入:console.time 和 console.timeend:

记录 DOM 的快照
获取当前状态下 DOM 的快照:
copy(document.documentElement.outerHTML);
每秒记录一次 DOM 快照,并打印到控制台:
doms = [];
setInterval(() => {
  const domStr = document.documentElement.outerHTML;
  console.log("snapshotting DOM: ", domStr);
  doms.push(domStr);
}, 1000);
监控网页中获得焦点的元素
(function () {
  let last = document.activeElement;
  setInterval(() => {
    if (document.activeElement !== last) {
      last = document.activeElement;
      console.log("Focus changed to: ", last);
    }
  }, 1700);
})();

找到所有加粗的元素
const isBold = (e) => {
  let w = window.getComputedStyle(e).fontWeight;
  return w === "bold" || w === "700";
};
Array.from(document.querySelectorAll("*")).filter(isBold);
调试当前选择的元素
$0 控制台中的内容是对元素检查器中当前选定元素的自动引用。
例如 ,我们可以检查当前所选元素的事件侦听器:getEventListeners($0):

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

调用并调试函数
在我们想要查找问题并进行详细调试的时候,一个简单的技巧就是先调用一下 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;
但是这个方法不能处理当代码直接调用 window.location.replace/assign 的情况,因为页面会在赋值后立即卸载,所以没有什么可以调试的。如果你仍然想要看到这些重定向的来源(并在重定向时调试你的状态),在 Chrome 中,你可以这样调试相关的方法:
debug(window.location.replace);
debug(window.location.assign);
调试属性读取
如果你有一个对象,想知道它的属性什么时候会被读取,可以在对象的 getter 中调用 debugger。例如,将 {configOption: true} 转换为
{ 
  get configOption() { 
    debugger; return true; 
  }
}
当你将一些配置选项传递给某个地方,并且想要看到它们如何被使用时,这个技巧非常有用。

使用 copy() 函数
Chrome 和 Firefox 浏览器都支持使用 console API 的 copy() 函数,可以直接将浏览器中的有趣信息复制到你的剪贴板,且不会有任何字符串截断,下面是一些你可能想要复制的有趣信息:
当前 DOM 的快照: copy(document.documentElement.outerHTML)
资源的元数据(例如:图像): copy(performance.getEntriesByType("resource"))
大型格式化的 JSON 块: copy(JSON.parse(blob))
你的 localStorage 的数据转储: copy(localStorage)
等等。
这个技巧可以在你需要将一些数据信息复制到剪贴板,以便你在其他地方使用或者进行分析的时候使用。

使用 monitor() 函数
你可以使用 Chrome 的 monitor 命令行方法来轻松追踪所有对类方法的调用。比如,给定一个 People类:
class People {
  eat(count) {
    /* ... */
  }
}
如果我们想要知道所有对 People 类所有实例的调用,将以下代码粘贴到命令行:
var p = People.prototype;
Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
然后你将在控制台获得输出:
function eat called with arguments: 2
如果你希望在任何方法调用时暂停执行,而不仅仅是打印到控制台,可以使用 debug 而不是 monitor。
如果你不知道类名,但你有一个实例,可以这样操作:
var p = instance.constructor.prototype;
Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
当你需要编写一个函数,对任何类的任何实例进行此类操作时,这个方法会非常有用。

绕过反调试
有时打开网页的 Devtools 你会发现可能会一直循环进入到一个 debugger 中,导致没法正常调试。
这可能就是网站给是增加的一点反调试的手段:

但这个绕过非常简单, 你只需要右键 debugger 的位置,点击 Never pause here ,就不会在这里进入断点了:

最后
大家这些技巧哪个最实用?欢迎来评论区留言。
用户评论