前端必备调试技能 - chrome 开发者工具 Souces 面板详解

4,795 阅读12分钟

最近发现有很多同学在调试代码的时候依旧是一个 log 打天下,着实是有些辛苦。本篇文章,咱们来介绍一下 chrome 开发者工具里一个相当好用、对测试相当友好但是被很多人忽略的面板:源代码(Source) 面板,让我们减轻负担,提前下班。

汉化切换

先简单提一下怎么把控制台切换成中文。打开控制台后点击右上角小齿轮进入设置,然后就可以找到语言选项,选择中文后重启控制台即可。

image.png

这个应该是最近几个大版本才更新出来的,所以如果你没有也不用难过,下面还是会用英文界面进行介绍,OK,话不多说我们现在开始。

左侧面板

首先我们打开 Souces 面板,可以看到是左中右布局,在左侧可以看到有几个选项卡:

image.png

在 Page 和 Content scripts 面板里可以看到当前页面正在使用的代码,平时用的不多(毕竟大多数代码都被压缩过了看不懂),真正实用的是后面两个:

image.png

咱们一个一个来说,首先是这个 Overrides(覆盖) 面板,它可以把本地的文件覆盖到你的网站上。emmmm,有啥用呢?还记得有时候我们直接在浏览器里调试样式,然后回头改了个代码,浏览器直接一个自动刷新,好了,辛辛苦苦改到一半的样式没了。

现在我们就可以用 Overrides 来解决这个问题:它会把你编辑的内容保存到本地,然后用这个本地文件覆盖线上加载的内容。

具体做法如下:先在 Overrides 面板里选择保存到本地哪个文件夹里,允许后就可以来到 Element 面板里找到我们要编辑的样式文件,点一下之后我们会跳会到 Sources 里,然后右键这个文件,选择保存到覆盖文件即可(Save for Overrides),然后我们的准备工作就做完了,具体操作见下面 gif:

动画.gif

注意上面 gif 里,我们保存了覆盖文件之后,Network 面板会有一个黄色感叹号,同时 Element 里也可以看到对应的样式文件链接前多了个图标,悬停上去就可以看到链接到的文件地址。

好了,现在我们就可以编辑了,随便改样式,之后点击刷新,我们会发现之前编辑的样式并没有消失:

动画 (1).gif

这个操作除了我们平时调试样式,还很适合在非开发机上操作。例如在客户机器上,这么搞一下之后实操+争吵后确定了客户想要的样式,就可以直接把保存到本地的样式拷贝拿回去用。

代码片段

左侧第二个实用功能是代码片段(Snippets):

image.png

这简直就是一个浏览器自带的 IDE。有很多人觉得它不实用,因为里边的代码跑一遍之后再执行就会报错说变量已定义:

image.png

只能跑一遍的代码要他有什么用?其实不然,只要把 var 换成 let 就可以解决问题了:

image.png

或者你的代码里很多 var,直接在外边包一个自执行函数也可以:

image.png

其实除了测试自己的代码,我们还可以搭配 chrome 插件 console-importer 解锁更强力的操作:在浏览器里使用第三方依赖!

例如下面这段代码,就可以直接在浏览器里搞出一个 lodash 的迷你 playground:

if (!('_' in window)) $i('lodash')
else {
    const result = _.map([1, 2, 3, 4, 5], i => i + 1)
    console.log(result)
}

很简单对吧,看一下全局有没有 lodash,没有的话就安装。具体效果可以看这个 gif:

动画 (2).gif

我大多数有点忘的 lodash api 都会来这里试一下(lodash 官方文档里的交互用例载入太慢了 ),除了 lodash 之外还有相当多的第三方包都可以用,比如 jq,react,vue 等等,大家自由发挥~

断点调试

页面中间的编辑器窗口就是我们调试的主场了,但是在调试之前我们还有一个问题要解决。

现在目标大多数前端框架都会有一个编译打包的过程,所以虽然我们可以通过左侧的 Page 面板找到页面使用的所有代码,但是我们会发现里边包含的内容基本都看不懂。

image.png

我知道怎么打断点,但是你得先告诉我代码在哪啊。所以首先,咱们先讲一下 如何定位问题代码,如果你已经很熟悉的话大体浏览一下即可:

顺带说一句,如果你使用 webpack 打包的话,打包前的网站源码实际上就放在 Pagewebpack:// 目录下。

image.png

如何进入断点调试

首先是最常用的办法,从报错信息入手,现在主流的前端框架都带 source-map,会自动校准调用栈。所以我们可以看一下异常的调用栈,找到其中我们眼熟的文件,点进来就是我们熟悉的内容了,然后就是左侧打红点并重新执行下问题操作,代码会在执行到你的红点处暂停。

rn报表.gif

但是报错信息里的调用栈就那几个,如果我们在报错信息里没有看到眼熟的文件名,都是一些 xxxdevtool 之类的文件,这时候可以使用第二种办法,点击右侧按钮条中最后一个按钮,并选中 Pause on caught exceptions(报错时暂停)。然后我们就可以在 Call Stack 面板里看到比报错信息里更详细的的调用栈。

rn报表.gif

又或者,你在这里也没找到具体的报错位置,或者你的代码根本没有报错,只是行为异常。这样我们就只能掏出最终武器(console.log 或者 debugger)。

使用 debugger 关键字可以直接让代码在 debugger 处暂停,而 console.log 在控制台中显示出的日志右侧也会标记出文件位置,我们可以点击跳进 Souces 面板并手动打断点:

image.png

对于大多数人来说,接触到的第一种调试方法基本就是 debugger 了,但是我在日常使用中直接打 debugger 反而最少,因为问题有可能不是在你电脑上复现的、或者没办法很方便的接触到源码、又或者复现问题需要点很多操作,回去改下代码页面就刷新了。想复现又要重新做一遍复现流程,就很烦。

所以说,下文中我们会更倾向于:不修改代码,尽量使用 devtool 本身提供的能力进行调试

条件断点和 log point

首先,请出我们的测试用例,很简单,闭包创建了一个点击回调,每次点击按钮时就会修改值,你也可以复制到 html 文件里一起试一试:

<script>
const createClick = function () {
    let val = 0;
    return () => {
        const valBox = document.getElementById('val');
        valBox.innerText = ++val;
    }
}

const onClick = createClick()
</script>

<body>
    <span id="val">0</span>
    <button onclick="onClick()">点此 +1</button>
</body>

现在我们来介绍第一个常用的断点变种:条件断点

顾名思义,就是给这个断点的触发加上一个条件。这对于调试那些会重复执行超多次的代码很有帮助。

放置条件断点的方式也很简单:在要添加断点的位置右键选择 Add Conditional Breakpoint 或者 右键已有的断点,选择 Edit breakpoint,将断点类型设置为 Conditional Breakpoint,然后输入任何 js 支持的条件表达式即可,表达式里支持的变量就是断点所在作用域能访问到的所有变量。

rn报表.gif

除了条件断点之外,我们还可以创建另一个叫做 log point(日志点?)的东西。这个特性是 chrome 73 里添加进来的,可以让我们少打 console.log,具体用法和条件断点类似:

gif.gif

这个功能特别实用,几乎所有可以写在 console.log 里的代码都可以写在这里,并且从上面这个 gif 中也可以看到,可以使用从全局、闭包、到当前作用域的所有可访问变量。直接干死 console.log 了属于是。

右侧面板

讲完了左边中间。咱们再来讲一下右边的这些面板,你可能经常看见,但是基本没怎么碰过:

image.png

首先需要说明一点,这里的大部分功能都是 断点调试 的一部分,也就是说,很多都要进入断点后才可以使用。接下来我们一个一个来看:

1、Threads

首先是最上面的 Threads(线程)。你可能会问,啊?浏览器里的 js 不是单线程么?

是的没错,但是现在我们有了 Web WorkersService Worker。所以前端也是会同时存在多线程的。而 Threads 就会标识出当前启动的线程:

image.png

2、Watch

还记得我们刚才介绍的 log point 么,Watch 字段就是断点调试中内置的 log。我们可以在这里新建表达式,进入断点后这里就会自动计算出表达式的值。

进入断点前:

image.png

进入断点后:

image.png

你可能会好奇为什么进入断点前的 val 会显示为 [object HTMLSpanElement],这个是 chrome 开发者工具的一个小特性(褒义),它会自动将设置了 id 的 dom 元素放在全局下供我们快速访问,而我们的测试用例里就设置了一个 id 为 val 的 span 元素。

你也可以在控制台里直接输入元素 id 来打印出对应的元素,例如上面的 val。

3、BreakPoints

这个面板里显示了所有存在的断点,我们可以在这里启用 / 停用指定断点:

image.png

4、Scope(常用)

这个面板显示了 当前断点所在的作用域,使用频率很高:

image.png

大体上可以分为以下四种:

  • Local本地作用域:代码当前 {} 内存在的变量,this 就可以在这里找到
  • Closure闭包作用域:代码当前所处闭包里的变量。注意名称后用括号注明了这是哪个闭包里的内容,因为闭包可以嵌套,所以可能出现多个 Closure 作用域。
  • Script代码标签作用域:当前 <script />标签里存在的变量,这个用的不多。
  • Global全局作用域:这个就不用解释了吧。

因为这个面板里可以找到 this 和闭包内容,所以在调试一些作用域问题的时候简直是神技,非常好使。

这个 Closure 在官中里被翻译成了 关闭,老机翻了。

5、Call Stack

调用栈,这个就不多说了。进断点之后可以看到当前的调用次序,点击能跳到对应的代码。

6、XHR/fetch Breakpoints

从这里往下的面板都是一些特殊断点的触发器了。而这个面板就是用来设置当发送包含对应 url 的请求时进入断点:一般都是输入一个 url 片段。如果不输入直接回车的话就是对所有请求启用断点。

image.png

但是由于我们一般都会使用一些第三方库来封装请求,所以这个功能一般都会搭配上面的 Call Stack 面板来定位实际发送请求的业务代码。

7、Dom Breakpoints

顾名思义,就是 dom 变更的时候进入断点。但是很多同学不知道怎么建 dom 断点,因为右上角没有看到小加号:

image.png

实际上建 dom 断点不是在这里,而是在 Element 面板里,我们右键一个 dom 元素,在菜单下面就可以看到 Break on,其中可以选择 subtree modifications(子树变更时触发)、attribute modifications(元素属性变更时触发)、node removal(元素移除时触发):

gif.gif

这个断点在查找一些底层、组件库问题或者性能调优的时候非常好使。特别是在找那些不知道啥时候被添加上的玄幻样式。

8、Global Listener

显示所有绑定到 window 的事件监听器,也可以找到对应的绑定代码:

image.png

在调试时,我们可以通过解绑事件来定位问题是哪个事件引发的:

gif.gif

有一点需要提一下,这个面板 只显示绑定到 window 上的监听器。如果你想查看 docuemnt 或者具体元素上的元素,需要到 Element 里选择对应元素,然后在右侧的 Event Listener 里查看,内容都是一样的,这里不再赘述:

image.png

有一说一,这个面板在调试代码的时候用的比较少,写爬虫的时候用的倒挺多

9、Event Listener Breakpoints

这个没啥好说的,就是给你列了一下所有的事件,你勾选哪个,对应事件触发时就会进入断点。

image.png

因为这里列的挺全,所以我一般都是忘记了某种事件叫啥的时候才会来这里查一下。这个写爬虫也挺常用

10、CSP Violation Breakpoints

用于在违反内容安全策略的时候进入断点,我没用过,就不当复读机了,有兴趣的可以去官方介绍看一下:

What's New In DevTools (Chrome 89) - Chrome Developers

总结

本文介绍了开发者工具里的 Sources 面板,包含了不少很实用却被被许多开发者忽略的功能,特别是代码片段、条件断点以及右侧的 Watch 表达式、Scope 作用域、Call Stack 调用栈面板。希望本文能让大家重新认识它,提高自己的开发效率,毕竟没人不喜欢早点下班!

大家有兴趣的话以后也可以分享一下其他实用的开发者功能,如果有帮助的话欢迎点赞~

参考