Chrome 调试技巧
这里收集日常开发中用到的 Chrome 调试技巧。
问题
基础
Chrome DevTools 有 8 个面板:
- 元素面板(Elements)
- 控制台面板(Console)
- 源代码面板(Sources)
- 网络面板(Network)
- 性能面板(Performanc)
- 内存面板(Memory)
- 应用面板(Application)
- 安全面板(Security)
提示:安装一些浏览器扩展也会新增【面板】
通用 - copying & saving
copy(...)
Console Panel:
可以通过全局方法 copy()
拿到任何你能拿到的资源
Store as global
Console Panel:
如果你打印了一堆数据,想复用,可以右键选择 “Store as global variable”(保存为全局变量)
依次命名为:temp1
、temp2
、temp3
、...
保存堆栈信息(Stack trace)
Console Panel:
把控制台打印出来的报错堆栈信息保存到一个文件,便于调试和沟通:
Copy HTML
Element Panel:
右击或者点击在 HTML 元素边上的省略号 (...) 就可以将它 copy 到剪贴板中(【Ctrl + C】也可以复制):
通用 - 快捷键和通用技巧
能直接快速提升开发效率的方式 —— 使用快捷键。
切换 DevTools 窗口的展示布局
- win
【Ctrl + Shift + D】
- mac
【CMD + Shift + D】
切换 DevTools 的面板
第 1 种:
- win
【Ctrl + [】
或【Ctrl + ]】
- mac
【CMD + [】
或【CMD + ]】
第 2 种:
- win
【Ctrl + 数字】
- mac
【CMD + 数字】
注意:第 2 种默认被禁用,要去设置开启:
递增/递减
使用上/下箭头键可调整数值,配合修饰键可递增/递减 0.1
、1
或 10
。
使用场景:CSS 属性实时调试
elements、logs、sources、network 的查找
这四个面板都可以通过以下快捷键查找:
- win
【Ctrl + F】
- mac
【CMD + F】
提示:Elements Panel 支持 XPath 查询
通用 - 使用 Command
DevTools 中的 Command 菜单类似于 WebStorm 的 Find Action
和 VSCode 的 Command Palette
:
启用:【CMD + Shift + P】
注:mac
【CMD + Shift + P】
很容易冲突,可以安装shortcutdetective
来解决(brew install --cask shortcutdetective
),参考:mac下vscode command+shift+p 打开命令面板快捷键失效
三种实用用途:
- 截图(screen):节点截图、全屏截图(Capture full size screenshot 👍)
- 切换面板(layout)
- 切换主题(themem)
通用 - 代码块的使用
直接在 Console Panel 执行:
或在 Source Panel 执行:快捷键【CMD + ENTER】或右键文件选择【Run】
PS:【CMD + K】清空 Console 输出
更快捷:
使用 Command Menu
来执行:
- 控制台【CMD + Shfit + P】唤起输入框(或【CMD + P】再按退格键也可以)
- 输入
!
选择要执行的 Snippet
console - console 中的 '$'
$0
用法
在 Element Panel 中,$0
是对我们当前选中的 html 节点的引用
$1
是上一次,$2
是上上次,...之道 $4
$
和 $$
用法
$
=document.querySelector
:用法相当于 jQuery 的$
$$
=document.QuerySelectorAll
:返回一个节点的数组
$_
用法
$_
引用上次执行的结果:
Math.random()
$i
用法
通过 Chrome 插件 Console Importer,可以在 Dev Tools 中直接使用 npm 包。
例如:只需运行 $i('lodash')
,几秒后就能获取到对应的库。
$i('lodash')
_.kebabCase('This is amazing!')
console - console 中的 'bug'?
使用 console.log()
打印对象时,如果在两次打印之间修改了对象,可能会发现修改前后的打印结果竟然一样。这种情况会影响调试。
为什么会这样?
- 因为 console 中打印的对象是以引用方式保存的,在实际展开查看前,对象的值可能已经发生改变。
如何解决?
- 打印对象的深拷贝
- 使用 Sources 面板的断点进行调试
- 使用
JSON.stringify()
将对象序列化后再打印
console - 异步 console
浏览器 API 大多基于 Promise,但在 Console 中使用 Promise 的 .then()
或 async/await
语法不太方便。
示例:
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json))
或者:
(async() => {
Response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
json = await Response.json()
console.log(json)
})()
但是注意!实际上 console.log 默认是被 async 包裹的!
也就是说可以直接用 await
:
Response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
json = await Response.json()
然后我们可以用异步 console 来获取一些有意思的信息:
// Storage 系统的 占用数 和 空闲数
await navigator.storage.estimate()
// 设备的 电池信息
console.table(await navigator.getBattery())
// 媒体能力
query = {type:'file', audio:{contentType: 'audio/ogg'}}
console.table(await navigator.mediaCapabilities.decodingInfo(query))
// Cache storage keys
await caches.keys()
console - Ninja console.log
当断点执行次数太多时,可以使用条件断点来只关注特定条件下的执行结果,这就是条件断点(Conditional breakpoints)
条件断点
右击行号,选择"添加条件断点",输入条件表达式即可设置条件断点。也可以右击已有断点编辑条件。
let a = 0
Array
.from(Array(15))
.map(Math.random)
.forEach((v, i) => {
a = a + v * i
})
🥷 忍者断点
条件断点的特点 :
- 每次执行到断点处都会判断条件
- 条件为 falsy 值时不会暂停执行
那么,可以把“判断语句”改为 console.log 语句!
console - 自定义格式转换器
关于 formatter
DevTools console 默认的对象转换方式可能不能满足我们的需求,此时可以使用 Custom Formatter 来自定义输出格式。
注意:要先去设置开启选项:
formatter 是一个对象,包含以下方法:
- header: 控制日志主要展示内容
- hasbody: 返回 true 时显示展开箭头
- body: 定义展开后的内容
一个基础的自定义 formatter:
window.devtoolsFormatters = [{
header: (obj) => {
return ['div', {}, `${JSON.stringify(obj, null, 7)}`]
},
hasBody: () => {
return false
}
}]
console.log(window.document)
自定义格式转换器实践
市面上有多种 custom formatter 可选,比如 immutable-devtools 仓库提供的 Immutable.js 结构展示方案。你也可以自己实现一个。
当遇到特殊结构的对象或需要区分的大量日志时,custom formatter 会很有用。
小技巧:对于不需要特殊处理的对象,在 header 方法中返回 null,让 DevTools 使用默认格式化方式。(?)
示例:
window.devtoolsFormatters = [{
header: function(obj){
if (!obj.__clown) {
return null; // header null:让 devtools 使用默认格式化方式来处理
}
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 - 对象 & 方法
介绍对象和方法的调试技巧。
queryObjects(对象查询)方法
如何查看当前上下文中存在哪些对象?可以使用 DevTools 的 queryObjects 函数。
class Person {
constructor(name, role) {
this.name = name;
this.role = role;
}
}
const john = new Person('Jonh', 'dad')
let kids = [
new Person('Mary', 'kid'),
new Person('Luke', 'kid'),
]
new Person('Lucius', 'uncle') // 不可用的:在代码执行后,对于它的引用并没有留存下来
queryObjects(Person) // 只有 3 个实例
monitor(监听)方法
monitor 是 DevTools 的方法,用于监控函数调用 - 当被监控的函数执行时,控制台会打印出函数名和参数。
class Person {
constructor(name, role) {
this.name = name;
this.role = role;
}
greet() {
return this.getMessage('greeting')
}
getMessage(type) {
if (type === 'greet') {
return `Hello, I'm ${this.name}!`
}
return type
}
}
monitorEvents(监听事件)方法
还有这个监听事件的方法,可以结合上面的做一些事情:
console - console 更多技巧
console.assert
当我们传入的第一个参数为 假 时,console.assert 打印跟在这个参数后面的值:
console.assert(assertion, msg [, subst1, ..., substN]);
示例:
增强 log 的阅读体验
// ❌ Bad
console.log(myName, pencilsCount, timestampNow, id);
// ✅ Good
console.log({ myName, pencilsCount, timestampNow, id });
// ✅ Better
console.table({ myName, pencilsCount, timestampNow, id });
console.table
// ❌ Bad
console.log(bigObject)
// ✅ Good
console.table(bigObject)
// ✅ Better(仅展示想看的字段)
console.table(bigObject, [keyName1, keyName2, ...])
console.dir
当你想查看 DOM 节点的 JS 对象属性时,console.log 只会显示 DOM 节点的 HTML 结构,这时可以使用 console.dir
代替 console.log
。
给 logs 加上时间戳
调用命令面板开启:
示例:
监测执行时间
console.time('标签')
开启计时器,console.timeEnd('标签')
结束并打印耗时- 可使用不同标签同时记录多个计时
给你的 console.log 加上 CSS 样式
使用 %c
可以为 console.log 添加 CSS 样式(样式写在第二个参数)。
让 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 作为回调函数,这样不仅代码更简洁,还能打印出所有参数:
❌ Bad
getMyLocation(v => console.log(v)) // => {coords: Coordinates}
✅ Good
getMyLocation(console.log) // => {coords: Coordinates} "pretty-accurate"