相关篇章
Chrome Devtools: Elements篇
概述
Sources面板用于资源检索、代码逻辑调试。
演示前置
示例
- ElementUI官网
其他篇章有的是以掘金为示例演示的,而掘金是服务端渲染(SSR),资源压缩,不易演示。
环境
- Chrome浏览器
- 版本 90.0.4430.93
操作释义
聚焦控制台
鼠标在控制台范围内点击一下,使后续操作上下文绑定在控制台中。
打开控制台
以ElementUI官网为示例讲述:
通过链接打开页面,通过F12或鼠标右键【检查】打开开发者工具控制台。
默认布局
- 资源管理器面板
- 该面板下又细分不同的面板,默认展示的是Page面板
- Page面板默认以域名分类,列出站点依赖的所有资源
- 代码编辑面板
- 在资源管理器面板中选中一个文件后,该文件的内容展示在该面板
- 代码Debugger面板
- 操作断点
- 查看断点上下文
Sources面板
该面板下的功能紧紧依赖着资源管理面板、代码编辑面板、调试面板。
只不过细分到下层面板管理不同的资源:
- Page面板
- 管理远程站点资源
- Filesystem面板
- 将Sources面板当IDE(代码编辑器),管理本地站点资源
- Snippets面板
- 管理浏览器持久化代码资源
我们以Page面板为主体讲述,其它小面板(Filesystem、Snippets)一带而过。
Page面板
面板布局1:资源管理器
默认布局的1位置即Page面板的全部内容,该面板列出了当前站点页面执行的所有资源,我们可以通过该面板获取以下站点信息:
- 技术栈:
- 可以从资源的关键代码查看
- 相关资源:
- 从资源列表中一眼能看出页面加载的资源类型
- 当前页面执行的自定义脚本,比如Snippets面板下定义的...
- 依赖域:
- 从资源分类上,依赖资源所在域一目了然
- 浏览器扩展程序
- 当前页面加载的浏览器扩展程序
- 为了干净的调试环境,排除扩展程序的干扰,所以,一般选用无痕模式调试
- 前提:没有开启扩展程序无痕模式下可用
面板布局2:代码编辑面板
在Page面板中点击任意资源,即可在默认布局2的编辑面板中看到资源详情。
上图点击了图片资源,可以看到该图片是一张二维码。
上图点击了CSS资源,在默认布局2位置的编辑面板中,可以点击左下角的 {} 按钮,进行代码美化 —— 根据当前CSS资源的大小,美化所需要的时间不同。
若资源太大,浏览器可能会因为CPU占用过高卡死。
这里我们做了一个试验,检索到顶部菜单的选择器,进行样式更新,可以实时地在页面上看到展示效果,甚至不需要保存。
然而,在这里修改的代码只是保存在内存中,刷新页面代码就还原了。
JS资源调试
这里,我们将Javascript资源单独讲述,因为在Devtools中JS资源调试的复杂度较高。
调试JS的场景
- 在编写代码过程中,查看未知参数的结构;
- 在编写、Bug修复过程中,运行结果与逻辑设计不符时,代码逻辑梳理;
调试JS的步骤
以修复Bug为例:
- 找出Bug复现的规律
- 熟悉代码的前提下,由规律出发,推断Bug复现的范围
- 在不同的范围打(条件)断点
- Step by Step的调试断点
- 根据调试的结果,不断的缩小Bug范围
- 缩小至找到确定的问题
- 针对找到的问题,提出解决方案
- 评估解决方案,选择合适的方案修复
Note:针对网络请求,断点时间过长会造成请求超时。
断点 vs. 日志
说到调试代码,常用的方式有两种:断点、日志;
断点Debugger | 日志Console |
---|---|
中断代码的执行 | 不中断代码的执行 |
查看中断代码那一时刻的上下文信息 | 查看代码执行结束的上下文信息 |
非侵入式 | 侵入式,将日志代码写入业务代码中 |
查看代码中断时刻所有的执行上下文信息 | 只能查看指定的打印信息 |
时效性:此时此刻的值 | 代码执行结束时指定信息的值,除非深拷贝 |
示例
在代码编辑面板中,只有针对Javascript的断点才能拦截执行成功,而针对DOM的断点,需要在Elements面板添加:DOM的操作,详情参见Elements篇。
下述以Chrome浏览器提供的官方调试代码为例:
打开控制台,从Panel面板中可以看到当前页面只有两个资源:HTML页面及相关的JavaScript。
其它的是我安装的Chrome扩展(没有使用无痕模式)。
从get-started.html中我们可以看到相关的HTML结构、Style样式及引入的JavaScript代码。
页面逻辑
输入Number1、Number2,点击按钮获得计算结果。
期望结果
计算获取Number1、Number2两个数字的和:1 + 1 = 2
实际结果
获取到Number1、Number2字符串的拼接:1 + 1 = 11
复现率
100%,说明是逻辑错误,而不是代码逻辑对某种边界没有覆盖的概率问题。
代码锁定Bug范围(嫌疑犯)
function updateLabel() {
var addend1 = getNumber1();
var addend2 = getNumber2();
var sum = addend1 + addend2;
label.textContent = addend1 + ' + ' + addend2 + ' = ' + sum;
}
由代码 label.textContent = addend1 + ' + ' + addend2 + ' = ' + sum;
及 1 + 1 = 11
的显示结果猜测,addend1
、addend2
看似没有问题,而sum
看似有问题,那我们就在sum
计算的地方打断点。
断点调试
在资源管理器中点击html引入的Javascript文件,找到相关代码,在行号位置点击一下,即添加断点,再次点击同一行号,取消断点。
点击按钮,触发函数调用,此时,我们可以看到当前执行上下文中的所有信息,如addend1
、addend2
的值,偷偷的执行F9(Step),可以看到sum
的结果。
诊断
从当前执行上下文可以看到addend1
、addend2
的值是字符串类型,而字符串做加法就是字符串的拼接,所以,结果没问题!!!
是的,运算结果没问题,那为什么不是我们期望的结果呢?
因为我们期望的是数字类型相加!!!
测试
聚焦控制台,通过ESC按键可以在当前面板开启/隐藏Console面板,在控制台的最下方。
借助断点将作用域限制在函数
updateLabel
内,通过Console面板验证自己的猜想,此时,Console面板可以访问函数updateLabel
的变量。
解决方案
那我们只要将
addend1
、addend2
的值转换为数字即可。
方案有很多种:
parseInt()
parseFloat()
Number()
-
1 * '1'
...
这里,我直接在代码编辑器面板中改动了getNumber1
、getNumber2
的方法,另其返回数字类型的数据。
保存以后,点击按钮,可以看到期望的结果。
通过右上角按钮,切换断点的激活状态,激活是深蓝色,取消激活是有透明度的蓝色。
取消激活状态下,断点不会拦截代码的执行。
在取消激活的状态下,可以尝试输入不同的值,检测逻辑的正确性。
简单的调试示例到此结束。
CSS、JavaScript调试对比
JavaScript调试 | CSS调试 |
---|---|
需要保存 | 无需保存 |
需要手动执行调用,不会自动执行 | 自动执行,实时效果 |
可断点拦截执行 | 断点无效 |
断点分类
上述调试Javascript的前提是要熟悉代码,比较适用于在自己编写的程序中调试。
若遇到其它的场景,如第三方库调试,则需要根据相应的场景,选择合适的断点组合使用。
断点类型 | 应用场景 |
---|---|
代码行断点 | 大体/清晰知道调试范围时使用 |
代码行条件断点 | 大体/清晰知道调试范围时,且只调试某指定条件分支下使用 |
代码行日志断点 | 非代码侵入式的打印日志 |
DOM断点 | 调试指定DOM的改变、移除或子节点的变动,详见Elements篇 |
XHR断点 | 调试URL包含指定字符串的请求 |
事件监听断点 | 调试指定元素事件触发逻辑时使用事件委托时使用比较麻烦推荐配合黑盒 + 无痕模式使用 |
错误断点 | 调试错误捕获时使用一般只用于调试未捕获的错误 |
代码行断点
通过问题复现能推测出明确的调试代码范围时使用。
在代码编辑面板的行号上右键或者直接点击该行号,即可添加代码行断点。
代码行条件断点
在有中介统筹分配处理逻辑或条件分支时使用,排除其它逻辑的干扰,聚焦关注点。
由上图可知,在代码编辑面板的行号上右键,选择Add Conditional Breakpoint...,即可添加代码行条件断点。
添加条件断点时,如果条件为true时,拦截代码执行,条件为false时,不会拦截。
在输入框至少有一个为空时,断点有效,输入框全不为空时,断点无效。
代码行日志断点
无需侵入源代码,实现非侵入式的日志打印
如上图,输入的格式是console.log函数的参数形式。
如上图,可以显式地使用console对象的其它方法打印日志
DOM断点
详情参见Elements篇
XHR断点
随手找个请求,添加到XHR断点,重载页面,在请求send的时候,会拦截代码逻辑,可以查看相关的请求参数。
事件监听断点Event Listener Breakpoints
若对代码不熟悉或在大长篇代码逻辑中,只是知道点击触发业务处理逻辑时,可以考虑事件监听器断点。
然而,在复杂的事件委托中,是一个噩梦。
Note:无痕模式下使用,浏览器扩展程序会产生干扰。
错误断点
开启错误断点,默认会拦截未处理的错误逻辑。
若选择Pause on caught exceptions
,则捕获的错误逻辑也会被拦截。
黑盒(Ignore list)
黑盒(旧版叫黑盒,新版浏览器改名为忽略列表)是一小功能,单独拉出来讲是因为不适合和其它分类合并。
使用该功能可以聚焦关注点,排除非核心或可信任代码的干扰。
因为示例是一个很简单地例子,这里借助Event Listener Breakpoints
讲述该功能地使用。
在Click事件上添加断点,点击Add Number1 and Number2
按钮,会在函数onClick
地首行进行执行拦截,OK!
重载页面,在代码编辑面板打开可信任的代码,右键添加Add script to ignore list
,再次点击Add Number1 and Number2
按钮,会发现代码逻辑完全执行结束,没有拦截。
上述场景对比,我们可以意识到,黑盒帮助我们聚焦相关逻辑代码,跳过一些可信任(认为不会出现Bug)的库,如:jQuery?
在一步步调试时,不会进入jQuery
库调试,聚焦我们自己的逻辑。
代码覆盖率
可以帮助我们查找无效(Never)代码。
通过代码编辑面板右下角的Coverage
,我们可以查看代码的覆盖率。
如上图,页面初始化执行时,代码未覆盖率为41.9%,即有41.9%的代码在页面初始化时未执行。
点击Add Number1 and Number2
按钮,执行了输入校验相关的代码逻辑,代码未覆盖率为17.3%,即仍有17.3%的代码未执行。
输入输入,点击Add Number1 and Number2
按钮,执行了计算相关的代码逻辑,代码未覆盖率为0%,即代码逻辑全部执行。
某种程度上可以帮助我们查找Never无效代码。
面板布局3:调试面板
单步执行
在调试代码的过程中,我们需要控制代码的执行:
如上图,添加三个断点,追加一个日志。
触发事件,点击Resume script execution
,会恢复代码的执行,直至遇到下一个断点。
若在拦截时,长按Resume script execution
,选择下拉按钮的第一项,会跳过后续的断点,将代码执行完整,可以看到日志执行了一次。
若在拦截时,长按Resume script execution
,选择下拉按钮的第二项,会在拦截处强制停止后续代码执行,可以看到后续逻辑中的日志没有执行。
重载页面,使页面恢复到初始状态。
调试面板最上排的工具控制断点拦截时代码的执行,Resume
按钮恢复代码的执行,直至遇到下一个断点。
若断点所在代码行的表达式是一个函数,step over
按钮会跳过函数的内部执行,如图第15行(15L),下一步直接执行到了16L。
若断点所在代码行的表达式是一个函数,step into
按钮会深入函数的内部执行,如图第15行(15L),下一步进入了函数内部,执行到了22L。
执行到了22L后,若不想再调试该函数inputsAreEmpty
,可以通过step out
跳出函数的执行,如下图,下一步直接跳出当前函数的执行,执行到16L。
思考:
- 若在22L,点击了
Step into
按钮,下一步会执行到哪? - 若在22L,点击了
Step through
按钮,下一步会执行到哪?
最后看一下step
按钮,step
按钮是实名的老实人,遇山爬山,遇海潜海,逻辑中的每一步都会走到,遇见函数就会深入函数代码执行每一步。
若逻辑中一个函数都没有,step over
、step into
、step out
、step
的行为和step
保持一致。
禁用(Deactivate)、停用(disable)断点
看下图,分别执行了禁用、停用,看明白了么?
上图,加入了两种类型的断点,第一次正常执行,三个断点都会执行的到。
第二次禁用了断点,点击Add Number1 and Number2
,代码执行完毕,断点都没有拦截。
第三次激活断点(还原禁用),停用了断点,点击Add Number1 and Number2
,代码拦截在了Click事件处理逻辑的首行,后续断点并没有拦截。
迷糊了?
综述:
停用是有作用域的,停用的只是代码行断点,其它类型的断点并没有停用。而禁用是全部类型的断点都不会触发。
监听
重载页面,移除所有的断点。
可以增加表达式在Watch中,监听执行各时间点的值。
函数调用的监听会影响代码逻辑的执行,如上图中的 inputsAreEmpty()
作为Watch表达式,Watch在需要更新展示的时候(时机:代码执行到表达式所在所用域),Chrome会自动调用该函数来获取函数最新返回值。
如果在 inputsAreEmpty()
的函数逻辑中添加断点,就会无限循环。
- 在
inputsAreEmpty()
代码行处添加代码行断点,函数体会执行三次- 第一次,到达作用域时的初始值
- 第二次
inputsAreEmpty()
本身执行 - 第三次
inputsAreEmpty()
执行后,更新Watch表达式的值 - 因为
inputsAreEmpty()
函数体内无断点,更新Watch表达式后,再也没有其它时机更新表达式
- 在
inputsAreEmpty()
函数体内添加代码行断点,函数体无限执行- 第一次
inputsAreEmpty()
在执行作用域初始化 - 第二次执行
inputsAreEmpty()
- 第三次执行
inputsAreEmpty()
后,更新Watch - 第四次更新Watch执行
inputsAreEmpty()
- 第五次执行
inputsAreEmpty()
后,更新Watch - 第六次...
- 第一次
所以,不推荐在Watch表达式中添加函数调用。
作用域
在拦截代码执行时,我们可以在调试面板的Scope章节看到当前作用域Local的变量,如上图中的this。
继续执行代码到return 语句,可以在当前作用域Local看到返回值Return Value
双击作用域中的值,可以改变当前的值。
Global作用域可以看到全局作用域的变量、方法。
默认状态下,Console面板只能访问到全局的变量、函数,而在断点拦截时,Console面板可以访问到当前作用域下的方法、变量。
调用栈
调用栈Call Stack,可以看到当前执行函数的来源,帮助我们溯源。
在当前执行函数上右键,点击 restart frame,可以让当前函数的逻辑重新执行,在大块代码段调试中使用较为便利,仅限于断点执行到的函数,已经走过的函数无效。
Filesytem面板
浏览器即代码编辑器
如上图,在本地启动服务打开某静态页面,在Sources面板下的Filesystem面板添加静态项目到workspaces。
通过Elements面板定位锚点到某DOM节点,在Styles面板直接调试样式,重载页面后,该样式依旧有效。
通过查看样式源码,可以发现Styles面板中调试的样式已经保存到磁盘覆盖原有的样式。
适用场景
- 静态页面的CSS、JavaScript
- 上图我们可以看到Filesystem中的一些文件的文件名有绿色小点
- 绿点表明浏览器打开的该文件已经与本地磁盘建立连接
- 在浏览器调试修改文件可以直接映射到本地磁盘
- 上图我们可以看到Filesystem中的一些文件的文件名有绿色小点
- 静态页面的HTML通过Elements面板右键 eidt HTML 无法保存磁盘
- Elements面板映射的是DOM Tree
- DOM Tree的产生受 HTML、CSS(content样式属性)、JavaScript动态处理的影响,所以,浏览器无法将DOM Tree的改变映射到对应的位置
【推荐】在结构、展现、动效(HTML、CSS、JavaScript)分离的静态页面项目中快速调试使用。
无效场景
- 使用构建工具打包的项目
- 即使使用sourceMap,复杂的文件映射关系使得浏览器和本地磁盘建立薄弱的连接
- 连接不可靠
Snippets面板
Snippets面板为我们提供跨标签、跨域名的Javascript测试性功能。
上图附了一段检测浏览器类型的Javascript脚本, Ctrl + S 保存后,可以打开其它页签,在Snippets面板中仍然能看到该脚本。
点击右下角的执行按钮或通过快捷键 Ctrl + Enter 执行这段脚本,可以在下方的Control面板看到执行打印出来的日志。
Snippets | Console |
---|---|
跨标签页可用 | 当前标签页可用 |
永久保存,除非手动删除 | 页面重载后清除 |