罗列一些浏览器很有用但是不明显的debug技巧,假定大家都对浏览器的开发者工具有一些了解为前提(前端开发很难不了解😄)。
1、高级的条件断点
相较于我们在意想不到的地方进行有副作用的表达式进行debug,其实可以从像条件断点
这样的基本功能中找到更多方法来帮助我们进行debug。
Logpoints / Tracepoints
例如,我们可以在断点处进行console.log
。Logpoints是一种可以打日志的非暂定断点。Microsoct Edge已经内置了这个功能,Chrome也已经在v73版本加入了该功能,Firefox还没有。但是,我们可以使用条件断点在任何浏览器里面模拟他们。
如果你想计算当前行代码执行了多少次,你可以用console.count
取代console.log
。
截止到2020年5月,所有主流的浏览器都支持了logpoint/tracepoint。
Watch Pane
你可以在watch pane中使用console.log
。例如,你想在每次断点的时候看一下localstorage的一个数据快照,那么你可以在watch pane里面创建一个console.table(localStorage)
:
或者你想在DOM变化以后执行一个表达式,你可以设置一个DOM变化断点:
然后添加watch表达式,例如,记录DOM的快照:(window.doms = window.doms || []).push(document.documentElement.outerHTML)
。现在,每当DOM子树发生变化,debugger会暂定执行,最新的DOM快照会追加到window.doms
的最后。(没有办法创建没有暂定执行的DOM断点)
追踪调用栈
假设,你有两个方法,一个是展示loding的show方法,另一个是hide这个loading的方法。然后,你在代码的某处你调用了show方法,没有调用hide方法。那么你怎么找到这个未成对的show方法的调用来源呢?在show方法的条件断点里面用console.trace
,运行代码,找到show方法的调用的堆栈的最后一条,点击调用即可定位到代码了。
更改程序行为
相较于在程序里添加有副作用的表达式,我们在浏览器就可以修改程序的执行逻辑。
例如,你可以重写getPerson
这个方法的入参id
,因为id=1
会返回true,这个条件断点将会暂停debugger,为了避免这个情况,添加false
到表达式里面就可以了。
快速进行性能统计
如果你想快速了解到某个代码执行耗时,你可以在条件断点里使用console的timing api,在代码开始的地方添加一个条件断点console.time('label')
,在代码结束的地方同样打上一个条件断点console.timeEnd('label')
。每次你执行这段代码,浏览器都会在log界面打印出运行时间。
函数方面
参数数量方面进行断点
当且仅当当前函数调用的时候使用了三个参数的时候进行暂停。当你有一些重载的函数十分有用。
参数未匹配方面进行断点
当且仅当当期函数调用的参数数量跟约定的不一致的时候,
使用时间
跳过页面加载
在页面加载5s内不要暂停:performance.now() > 5000
,当你只想对页面加载以后的逻辑进行暂停的时候非常有用。
跳过N秒
接下来5s内遇到断点不要暂定执行,但之后的都需要暂停:window.baseline = window.baseline || Date.now(), (Date.now() - window.baseline) > 5000
通过这句来进行逻辑的重置:window.baseline = Date.now()
使用CSS
基于Css的值来进行暂停,例如,当前仅当页面body为红色背景色的时候进行暂停: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 only)
当你右键选择了"Never Pause Here",Chrome将会创建一个条件为false且永不通过的断点,这样将会使debugger在这行代码这里永不暂停。
当你想免除XHR的断点中某行的时候会非常有用,例如忽略我们已经抛出的异常场景。
自动示例ID
将类的每个实例自动设定一个唯一的ID,你可以在类的构造函数中这样做:(window.instances = window.instances || []).push(this)
然后可以在类的方法里面检索这个唯一的ID: window.instances.indexOf(instance)
编程式触发中断
通过一个全局布尔值来控制一个或某几个条件断点:
然后通过编程的方式修改这个布尔值
- 手动改:
window.enableBreakpoints = true;
- 从其他断点中修改:
- 从setTimeout中控制:
setTimeout(() => (window.enableBreakpoints = true), 5000);
- 等等
2、监控类的调用(Chrome Only)
你可以使用Chrome的monitor
命令行方法很轻松的跟踪到所有调用该类的方法。举例说明,有一个类Dog
ts
class Dog {
bark(count) {
/* ... */
}
}
如果我们想知道所有Dog的实例的调用,在命令行里面粘贴这些代码:
ts
var p = Dog.prototype;
Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
当您想为任何类的任何实例(而不仅仅是Dog)编写一个这样做的函数时,这很有用。
3、调用并调试方法
当你想在调用方法之前在console里面进行调试,可以用debugger。例如
ts
function fn() {
/* ... */
}
在console中:
cmd
debugger; fn(1)
然后"step into next function call"就可以debugfn
的实现了。
当您不想手动找到fn的定义并添加断点时,或者当fn动态绑定到函数并且您不知道源在哪里时,这很有用。在Chrome中,您还可以选择在命令行上调用debug(fn),每次调用它时,调试器都会暂停fn内部的执行。
4、当URL变化时暂停执行
在一个单页应用中当URL发生变化的时候暂停执行(例如,当页面路由发生变化):
ts
const dbg = () => {
debugger
}
history.pushState = dbg;
history.replaceState = dbg;
window.onhashchange = dbg;
window.onpopstate = dbg;
创建一个暂停执行而不中断导航的dbg版本是留给读者的练习。
此外,请注意,当代码直接调用window.location.replace/assign
时,这不起作用,因为页面将在分配后立即卸载,因此没有什么可调试的。如果你仍然想查看这些重定向的来源(并在重定向时调试你的状态),你可以在Chrome中调试相关方法:
ts
debug(window.location.replace)
debug(window.location.assign)
5、调试属性的读取
如果你有一个对象并且想知道对象里面的属性什么时候被读取了,使用对象的getter并调用debugger。例如,将{configOption: true}
改成{get configOption(){ debugger; return true }}
(要么在源码改,要么在条件断点中改)。
当你将一些配置选项传递给某个东西,并且你想看看它们是如何使用的时,这很有用。
6、使用copy(Chrome & Firefox Only)
你可以通过console中直接执行copy命令将浏览器里面的信息拷贝到剪切板中,这样你可以在任何地方查看这部分内容。你可能感兴趣的内容有:
- 当前dom的快照:
copy(document.documentElement.outerHTML)
- 资源的元数据:
copy(performance.getEntriesByType("resource"))
- 大的JSON blob流,格式化后:
copy(JSON.parse(blob))
- localStorage的转存:
copy(localStorage)
- 等等
7、调试HTML/CSS
console确实能帮我们解决关于HTML和CSS的相关问题。
无JS影响的检查DOM
在DOM检查器中时,按ctrl+\(Chrome/Windows)可随时暂停JS执行。这允许您检查DOM的快照,而不必担心JS会更改DOM或事件(例如mouseover)会导致DOM从您的下面更改。
检查不好定位到的元素
假如你想检查一个只在某些条件下才会展现的元素,此时你需要将鼠标移动到该元素上的时候才能进行检查,但是有些时候你想放上去的时候,它就消失了,就像下面这样:
想要检车这样的元素,你可以将这段代码拷贝到console中:setTimeout(function() { debugger; }, 5000)
。这给了你5s来触发这个UI,一旦5s结束,js的执行将会暂停并且你的元素也不会消失,你可以自由地移动你的鼠标并且检查这个元素了。
一旦JS执行暂停,你可以检查元素,编辑样式,console中执行命令等。当你需要检查将鼠标放在某处进行hover或者focus才能展示的元素时,这确实十分有用。
记录DOM快照
记录当前DOM的快照
ts
copy(document.documentElement.outerHTML);
每一秒记录一次DOM的快照
ts
doms = [];
setInterval(() => {
const domStr = document.documentElement.outerHTML;
doms.push(domStr)
}, 1000);
或者只是转存到console
ts
setInterval(() => {
const domStr = document.documentElement.outerHTML;
console.log("snapshotting DOM: ", domStr)
}, 1000)
监控选中的元素
ts
(function(){
let last = document.activeElement
setInterval(() => {
if(document.activeElement !== last) {
last = document.activeElement
console.log("Focus changed to: ", last)
}
}, 100)
})()
查找粗体元素
ts
const isBold = (e) => {
let w = window.getComputedStyle(e).fontWeight;
return w === 'bold' || w === "700"
}
Array.from(document.querySelectorAll('*')).filter(isBold)
或者是当前选中元素的子元素进行查找
ts
Array.from($0.querySelectorAll('*')).filter(isBold)
引用当前选定元素
$0
在console中意为当前选中元素,在Chrome和Edge中你可以通过$1
访问上一次选中的元素,$2
为上上次选中的元素,以此类推。
在Chrome中你还可以获取当前选中元素的监听事件:getEventListeners($0)
,例如
监听元素的事件(Chrome Only)
调试选中元素的所有事件:monitorEvents($0)
调试选中元素的特定事件:monitorEvents($0, ["control", "key"])
8、总结
文章中还是提到了很多有效地调试能力,让我印象深刻的有:无断点的logpoint,快速统计代码性能,打印dom快照,监控不好定位的元素等,这些功能需要多使用才能孰能生巧,也是时候放弃在代码里添加有副作用的代码进行调试的办法了。那么,不知道你心水哪些功能呢?