console 中的 ‘$’
$0
在 Chrome
的 Elements
面板中, $0
是对我们当前选中的 html
节点的引用。理所当然,$1
是对上一次我们选择的节点的引用,$2
是对在那之前选择的节点的引用,等等。一直到 $4
。
$ 和 $$
如果你没有在 App
中定义过 $
变量 (例如 jQuery
),它在 console
中就是对这一大串函数 document.querySelector
的别名。
$$
能节省更多的时间,因为它不仅执行 document.QuerySelectorAll
并且它返回的是:一个节点的 数组 ,而不是一个 Node list
。
$_
$_
是对上次执行的结果的 引用 :
$i
现在的前端开发过程,离不开各种 npm
插件,在 Dev Tools
里面也能使用 npm
插件。
有时你只是想玩玩新出的 npm
包,现在不用再大费周章去建一个项目测试了,只需要在 Chrome插件:Console Importer 的帮助之下,快速的在 console
中引入和测试一些 npm
库。
运行 $i('lodash')
或者 $i('moment')
几秒钟后,你就可以获取到 lodash / momentjs
了:
console.log 的 “bug”
一般来说,我们会使用 console.log()
来打印某个对象,并且,两次打印之间,还会对这个对象进行修改,最后我们查看打印的结果发现,修改前的打印和修改后的打印,竟然是一样的?这样出乎意料的情况,让我们难以继续 console.log
的调试。
口说无凭,举个例子把:
console
中打印出的对象,在你打印出他内容之前,是以引用的方式保存的。
知道了原因,对应的就知道该怎么处理这样的情况了:
- 打印一个从这个对象复制出来的对象。
- 使用资源面中的断点来调试
- 使用
JSON.stringify()
方法处理打印的结果
异步的 console
你可以直接使用 await
:
事实上,在 Console
中使用 promise
比在源码中使用起来还要简单!
用你的异步 console 来看一些更酷的东西
Storage
系统的占用数和空闲数
await navigator.storage.estimate()
设备的电池信息
**
为了达到更好的效果,我们将这个技巧和前几天中提到的 console.table
来合并使用。敲黑板:这是一条不推荐使用的API,尽管看起来这么酷炫…
媒体能力
Cache storage keys
(注:Cache storage keys 一般用来对 request
和 response
进行缓存)
Ninja console.log (忍者打印)
Conditional breakpoints 条件断点
- 右击行号,选择
Add conditional breakpoint...(添加条件断点)
- 或者右击一个已经设置的断点并且选择
Edit breakpoint(编辑断点)
- 然后输入一个执行结果为
true
或者false
的表达式(它的值其实不需要完全为true
或者false
尽管那个弹出框的描述是这样说的)。
在这个表达式中你可以使用任何这段代码可以获取到的值(当前行的作用域)。如果条件成立,这个断点就会暂停代码的执行:
The ninja(忍者) console.log
得益于条件断点, console.log
也有了新玩法:
- 每一个条件都必须经过判断 - 当应用执行到这一行的时候进行判断
- 并且如果条件返回的是
falsy
的值(这里的falsy
并非是笔误,falsy
指的是被判定为false
的值,例如undefined
),它并不会暂停..
与其在你的源码的不同地方去添加 console.log
/ console.table
/ console.time
等等,不如你直接使用条件判断来将它们“连接”到 Source
面板中。 这样的话,它们会一直执行,并且当你不再需要它们的时候,在 Breakpoints section
会清晰的列出它们。点两下鼠标你就可以把所有的都移除,就像一堆忍者一样突然消失!
这个技术在调试生产环境的问题时同样很有用,因为你通过这样的方式轻松将 console logs
插入到 source
里。
自定义格式转换器
大多数的情况下,我们习惯使用 DevTools
的 console
默认对 object
的转换,但有时候我们想用与众不同的方式来处理。 那我们就可以自定义输出对象的函数,它通常被称为 Custom Formatter
。
请注意: 在我们写一个之前,需要在
DevTools
进行设置 (在DevTools
的⋮
下拉框找到设置,或者按下F1
) 中把对应的设置打开:
formatter
长什么样呢? 它是一个对象,最多包含三个方法:
header
: 处理如何展示在console
的日志中的主要部分。hasbody
: 如果你想显示一个用来展开对象的▶
箭头,返回true
body
: 定义将会被显示在展开部分的内容中。
一个基础的自定义 formatter
例子里移除了循环的结构的错误处理,让它看起来更加简洁
header
方法返回了一个 JsonML (注: JsonML
: JSON Markup Language
- JSON
标记语言) 数组,由这些组成:
- 标签名
- 属性对象
- 内容 (文本值或者其他元素)
(如果看起来很眼熟的话,那可能是因为你之前写过一些 React 代码 :D)
在输出的时候,这个简单的 formatter
对于每一层嵌套,直接以 7
个空格的缩进打印这个对象。所以现在我们的输出看起来是这样:
自定义格式化转换器的应用实践
现有好几种 custom formatter
可供选择,例如:你可以在这个 immutable-devtools 仓库中找到对于 Immutable.js 结构的完美展示。但你同样可以自己造一个。
一般来说,每当你遇到结构不寻常的对象时,或大量的日志(最好避免这样的情况,但是有时候很有用)而你想从中做区分时,你可以采用 custom formatter
来处理。
一个很实用的窍门:直接将你不关心,不需要区别对待的对象过滤出来,直接在 header
方法里面 return null
。让 DevTools
使用默认的格式化方式来处理这些值。
撇开实用性,我们还可以找点乐子:这是一个关于 console
的蠢萌例子:它叫做 console.clown()
:将打印对象进行转换,而且在对象前面加上一个 emoji
表情 …
源码在这里为了方便大家尝试,源码贴在下面:
window.devtoolsFormatters = [{
header: function(obj){
if (!obj.__clown) {
return null;
}
delete obj.__clown;
const style = `
color: red;
border: dotted 2px gray;
border-radius: 4px;
padding: 5px;
`
const content = `🤡 ${JSON.stringify(obj, null, 2)}`;
try {
return ['div', {style}, content]
} catch (err) { // for circular structures
return null; // use the default formatter
}
},
hasBody: function(){
return false;
}
}]
console.clown = function (obj) {
console.log({...obj, __clown: true});
}
console.log({message: 'hello!'}); // normal log
console.clown({message: 'hello!'}); // a silly log
如你所见,我使用 console.clown
方法打印出来的对象被添加了一个特殊的属性,便于将它区分出来,并且在 formatter
中对它区别处理,但在大部分现实的案例中,这样更好:比如检查这个对象是不是某一个特殊类的实例等等。
对了,clown
打印出来了什么东西呢? 在下面:
对象&方法
queryObjects (对象查询)方法
假如我们有这样一段代码,我们在里面定义了一些对象。DevTools
里的 queryObjects
函数可以展示在 特定的时刻 + 特定的执行上下文 有哪些对象。
请注意,列表中创建的最后一个对象是不可用的 : 在代码执行后,对于它的引用并没有留存下来,也就是说,我们只有
3
个person
对象:
monitor (镜像)方法
monitor
是 DevTools
的一个方法, 它能够让你 “潜入”
到任何 _function calls(方法的调用)
中:每当一个 被潜入
的方法运行的时候,console 控制台
会把它的实例打印出来,包含 函数名 以及 调用它的参数 。我们把前面例子里面的 Person
类拿过来,并且给它扩展两个方法:
class Person {
constructor(name, role) {
this.name = name;
this.role = role;
}
greet() {
return this.getMessage('greeting');
}
getMessage(type) {
if (type === 'greeting') {
return `Hello, I'm ${this.name}!`;
}
}
}
如你所见,greet
方法通过一个特殊的参数来执行 getMessage
方法,让我们看看对 getMessage
方法进行追踪会产生什么结果:
这样做,会让我们少写很多 console.logs
!
monitorEvents (镜像事件)方法
在上文中,我们讨论了用 monitor
方法来监听函数,其实还可以使用名为 monitorEvents
的方法,对 events
做一样的事情:
console 中骚操作
console.assert
在 MDN 中是这样定义的
console.assert(assertion, obj1 [, obj2, ..., objN]);
console.assert(assertion, msg [, subst1, ..., substN]); // c-like message formatting
当我们传入的第一个参数为 假 时,
console.assert
打印跟在这个参数后面的值。
这个方法适用于什么情况呢?举个栗子:
通过它,你可以摆脱令人讨厌的 if
表达式,还可以获得堆栈信息。
请注意,如果你使用的
NodeJS
版本≤ 10.0
,console.assert
可能会中断后面代码的执行,但是在.10
的版本中被修复了(当然,在浏览器中不存在这个问题)
增强 log 的阅读体验
有时即使你 console.log
一个简单的变量,你可能会忘记(或混淆)哪一个是那个。那当你有不同的变量需要打印的时候,阅读起来会更费劲。
假如有这么一堆你想要输出但看起来并不易读的数据:
“哪一个值对应哪一个变量来着?”
**
为了让它变得更加易读,你可以打印一个对象 - 只需将所有 console.log
的参数包装在大括号中。感谢 ECMAScript 2015
中引入了 enhanced object literal(增强对象文字面量)
,所以加上 {}
已经是你需要做的全部事情了:
console.table
如果有一个 数组 (或者是 类数组 的对象,或者就是一个 对象 )需要打印,使用 console.table
方法将它以一个漂亮的表格的形式打印出来。它不仅会根据数组中包含的对象的所有属性,去计算出表中的列名,而且这些列都是可以 缩放甚至 还可以排序。
**
如果你觉得展示的列太多了,使用第二个参数,传入你想要展示的列的名字:
对于后台而言,只有
node
版本大于10
以上,console.table
才能起作用
table 和 {} 的配合
console.dir
你想要打印一个 DOM
节点。 console.log
会将这个交互式的元素渲染成像是从 Elements
中剪切出来的一样。如果说你想要查看 这个节点所关联到的真实的js对象,并且想要查看他的 属性 等等。在那样的情况下,就可以使用console.dir
。
给 logs 加上时间戳
我们总是需要打印各式各样的信息,之前我们讨论了如何让输出的信息更加直观,但是如果我们需要打印相关的时间信息呢?这就用到了计时的相关操作。
如果你想要给你的应用中发生的事件加上一个确切的时间记录,开启 timestamps 。你可以在设置(在调试工具中的 ⋮
下拉中找到它,或者按下 F1
)中来开启或者使用 Commands Menu:
监测执行时间
与其在所有事上展示一个时间戳,或许你对脚本中的特殊的节点之间执行的时间跨度更加感兴趣,对于这样的情况,我们可以采用一对有效的 console
方法。
console.time()
— 开启一个计时器console.timeEnd()
— 结束计时并且将结果在console
中打印出来
如果你想一次记录多件事,可以往这些函数中传入不同的标签值。(例如: console.time('loading')
, console.timeEnd('loading')
)
给你的 console.log 加上 CSS 样式
如果你给打印文本加上 %c
那么 console.log
的第二个参数就变成了CSS
规则!这个特性可以让你的日志脱颖而出(例如 Facebook 在你打开 console
的时候所做的一样)
让 console.log 基于调用堆栈自动缩进
配合 Error
对象的 stack
属性,让你的 log
可以根据堆栈的调用自动缩进:
function log(message) {
console.log(
// 这句话是重点当我们 new 出来的 Error 对象时,会匹配它的stack 信息中的换行符,换行符出现的次数也等同于它在堆栈调用时的深度。
' '.repeat(new Error().stack.match(/\n/g).length - 2) + message
);
}
function foo() {
log('foo');
return bar() + bar();
}
function bar() {
log('bar');
return baz() + baz();
}
function baz() {
log('baz');
return 17;
}
foo();
运行结果如下:
直接在回调中使用 console.log
是不是经常有这样的情况,就是我确定要将什么传递给回调函数。在这种情况下,我会在里面添加一个 console.log
来检查。
有两种方式来实现:
- 在回调方法的内部使用
console.log
- 直接使用
consolelog
来作为回调方法。
我推荐使用第二种,因为这不仅减少了输入,还可能在回调中接收多个参数。(这在第一个解决方案中是没有的)
使用实时表达式
在本文形成的不久前,DevTools
在 Console
面板中引入了一个非常漂亮的附加功能,这是一个名为 Live expression
的工具。只需按下 “眼睛” 符号,你就可以在那里定义任何 JavaScript
表达式。 它会不断更新,所以表达的结果将永远,存在 :-)。同时支持定义好几个:
参考
【1】官方文档
【2】50 个 Chrome Developer Tools 必备技巧 | DevOpen.Club 出品【视频】
【3】Chrome调试技巧 | 前端九部 - 入门者手册2019
【4】你不知道的 Chrome 调试技巧(掘金小测)