Menu

JavaScript console 与 DevTools:console.log 之外的调试技巧

掌握这些 console 方法和 DevTools 功能,调试 JavaScript 会比到处加 console.log 快得多。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

控制台不只有 log

console.log 是大多数人学会的第一个调试工具,也是很多人一直没放下的工具。它确实管用,但 console 对象还有十几个其他方法,能让调试更快、更清晰。而一旦你熟悉了真正的 DevTools(断点、调用栈、监视表达式),你用 log 的次数就会比以前少很多。

先快速浏览一下:

四者都打印到同一个控制台,但浏览器会用不同的样式显示:警告带黄色背景,错误显示为红色并带图标,两者都附带堆栈跟踪。DevTools 中的筛选器可以隐藏或单独显示每个级别,当应用输出成百上千条消息时,这就很重要了。

偷懒地打印多个值

想同时查看几个变量时,不要把它们拼接成一个字符串。把它们作为单独的参数传入,或者更好的做法是放进一个对象里,让每个值都带上标签:

{ user, count } 这个技巧用的是对象简写:变量名成为键。在控制台里你会看到 { user: {...}, count: 3 },还可以展开 user 查看它。你不会因为转成字符串而丢失对象的结构。

用 console.table 查看对象数组

面对一个对象数组时,console.log 给你的是折叠成一团的输出。console.table 则会把它显示成真正的表格:

在浏览器控制台中,第一次调用会打印全部三行,列可以排序。第二次调用只显示 name 和 role 两列。对于任何数据形态的内容(API 响应、查询结果、解析后的 CSV),这都能极大地改善体验。

console.dir 与 console.log

两者看起来很像,但对 DOM 元素的处理方式不同:

const el = document.querySelector("button");

console.log(el);   // prints the HTML: <button>Click me</button>
console.dir(el);   // prints the JS object view with all properties

log 把元素格式化成 HTML。dir 则展示对象本身:每个属性、每个事件处理函数、每个计算样式的引用。当你想知道一个元素有哪些方法或属性时,就用 dir。

给相关输出分组

长时间的调试会让控制台充满噪音。console.group 和 console.groupEnd 可以把相关的消息包进一个可折叠的块里:

每次调用都会生成一个可以折叠的命名分组。如果你希望分组默认是折叠的,就用 console.groupCollapsed,这样你只需展开那些看起来可疑的分组。

用 console.time 计时

要做快速的性能检查,console.time 和 console.timeEnd 很难被超越:

time 和 timeEnd 中的标签必须一致。你可以用不同的标签同时运行好几个计时器。如果问题比“这个循环慢不慢”更复杂,就改用 DevTools 中的 Performance(性能)面板,它会记录完整的时间线和火焰图。

断言:只在出问题时输出

console.assert 只在条件为假值时才打印。它可以让你在代码中安静地保留一些健全性检查,一切正常时不会刷屏:

适合用于那些理应始终成立的不变量。不适合那些可能合理失败的情况,对于后者,应该抛出错误。

按需查看堆栈跟踪

console.trace 会打印当前的调用栈,而不会抛出任何东西。当你想弄清楚是谁调用了某个函数时,它很方便:

输出显示 inner → outer → (top level)。在真实的应用中,你就是靠它发现自己正在调试的点击处理函数竟然被三个不同的地方触发。

debugger 语句

暂停 JavaScript 执行最快的方法只需要一个词:

function computeTotal(items) {
    const subtotal = items.reduce((s, i) => s + i.price, 0);
    debugger;
    return subtotal * 1.08;
}

当 DevTools 打开时,debugger; 就像一个断点:执行会在这一行暂停,你可以使用完整的调试器,包括作用域中的变量、调用栈、单步跳过和单步进入,以及针对当前状态对表达式求值。当 DevTools 关闭时,debugger; 什么也不做。

第一次用真正的调试器代替 console.log 时,你会觉得像拥有了超能力。你能看到作用域中的每一个变量,而不必事先决定要打印什么。你可以逐步执行条件语句,观察哪个分支被执行。修复一个棘手的 bug,从几分钟缩短到几秒钟。

提交代码前删掉 debugger 这一行;更好的做法是直接在 DevTools 的 Sources(源代码)面板中点击行号设置断点。

值得了解的 DevTools 技巧

有些功能,很多人好几年都没发现:

  • 条件断点:在 Sources 面板中右键点击行号,设置一个条件,比如 user.id === 42。只有条件为真时断点才会触发。
  • 日志点:在同一个菜单中选择“添加日志点”。它会打印一条消息,既不暂停执行,也不用修改你的代码。
  • 控制台中的 $_:上一次求值的表达式结果。运行一段代码,然后用 $_ 拿到它的结果。
  • $0:Elements(元素)面板中当前选中的元素。$0.textContent 可以查看你点选的任何元素。
  • 存为对象:在控制台中右键点击任意值,选择“存储为全局变量”。你会得到 temp1、temp2 等变量,可以随意试验。
  • Network 面板 → 以 fetch 格式复制:把任意请求变成一个 fetch() 调用,你可以粘贴到控制台中修改。

这些都不是必需的。但一旦形成肌肉记忆,每一个都能帮你节省时间。

上线前清理干净

残留的 console.log 调用在开发环境中无伤大雅,在生产环境中就成了噪音。下面几个习惯会有帮助:

  • 用一条 lint 规则(ESLint 中的 no-console)标记零散的日志,并为 warn 和 error 设置例外。
  • 把详细日志包在一个判断里:if (process.env.NODE_ENV !== "production") console.log(...)。
  • 跟踪级别的输出优先使用 console.debug,大多数打包工具和日志聚合工具都能把它过滤掉。
  • 更好的做法:使用一个小型的日志模块(或者像 debug 这样的库),这样无需修改代码就能开启或关闭某类日志。

打印日志并不是零成本的。每次调用都要序列化参数并写入缓冲区。在频繁执行的循环中,一条被遗忘的日志就可能明显拖慢速度。

下一步:正则表达式

你会花大量时间调试处理字符串的代码,而这类代码很多都涉及正则表达式:这是语言中最紧凑、也最晦涩的特性。下一章会从一次轻松的入门之旅开始,介绍 JavaScript 正则表达式以及使用它们的方法。

常见问题

console.log 和 console.dir 有什么区别?

console.log 使用浏览器默认的格式打印值,对 DOM 元素来说,就是把它们显示成 HTML。console.dir 始终以 JavaScript 对象的形式打印,列出它的属性。当你需要查看元素的属性而不是它的标记时,就该用它。

不用 console.log,如何在 Chrome DevTools 中调试 JavaScript?

打开 Sources(源代码)面板,找到你的文件,点击行号设置断点。代码执行到那一行时会暂停,你可以查看变量、逐步执行代码,并在控制台中对表达式求值。你也可以在代码里写一条 debugger; 语句,从源码中触发断点。

如何测量 JavaScript 代码的运行时间?

用标签一致的 console.time('label') 和 console.timeEnd('label') 把代码包起来。控制台会以毫秒为单位打印耗时。如果需要更详细的性能分析,就用 DevTools 的 Performance(性能)面板录制一张火焰图,记录运行过的所有内容。

console.table 有什么用?

console.table 会把数组和对象在控制台中显示成可排序的表格,比嵌套的对象输出容易浏览得多。它最适合对象数组:每个对象变成一行,它的键变成列。你还可以传入第二个参数,限定显示哪些列。

Coddy 编程语言插图

和 Coddy 一起学编程

开始