让你 nodejs 水平暴增的 debugger 技巧
共 4625字,需浏览 10分钟
·
2021-07-12 06:30
学习 nodejs 最重要的是什么?可能每个人都有自己的答案。
我觉得学习 nodejs 除了要掌握基础的 api、常用的一些包外,最重要的能力是学会使用 debugger。因为当流程复杂的时候,断点调试能够帮你更好的理清逻辑,有 bug 的时候也能更快的定位问题。
狼叔说过,是否会使用 debugger 是区分一个程序员 nodejs 水平的重要标志。
本文分享一下 debugger 的原理和 vscode debugger 的使用技巧。
debugger 原理
运行 nodejs 代码的时候,如果带上了 --inspect
(可以打断点) 或者 --inspect-brk
(可以打断点,并在首行断住) 的参数,那么就会以 debugger 的模式启动:
可以看到,node 启动了一个 web socket 的 server,地址是:ws://127.0.0.1:9229/78637688-e8e0-4582-80cc-47655f4bff66
为什么 debugger 要启动一个 websocket server 呢?
debugger 的含义就是要在某个地方断住,可以单步运行、查看环境中的变量。那么怎么设置断点、怎么把当前上下文的变量暴露出去呢,就是通过启动一个 websocket server,这时候只要启动一个 websocket client 连接上这个 server 就可以调试 nodejs 代码了。
v8 debug protocol
连上之后呢,debugger server 和 debugger client 怎么交流?这就涉及到了 v8 debug protocol。
通过两边都能识别的格式来交流,比如:
在 test.js 的 100 行 设置断点:
{
"seq":118,
"type":"request",
"command":"setbreakpoint",
"arguments":{
"type":"script",
"target":"test.js",
"line":100
}
}
然后查看当前作用域的变量:
{
"seq":117,
"type":"request",
"command":"scope"
}
执行一个表达式:
{
"seq":118,
"type":"request",
"command":"evaluate",
"arguments":{
"expression":"a()"
}
}
之后继续运行:
{
"seq":117,
"type":"request",
"command":"continue"
}
通过这种方式,client 就可以告诉 debugger server 如何执行代码。
debugger client
debugger client 一般都是有 ui 的(当然,在命令行里面通过命令来调试也可以,但一般不这么做)。常见的 js 的 debugger client 有 chrome devtools 和 vscode debugger 等。
我们写一个简单的 js 脚本,通过 node --inspect-brk 跑起来:
可以看到它启动在了 9229 端口,
然后,我们分别通过两种 client 连上它。
chrome devtools
在 chrome 地址栏输入 chrome://inspect,然后点击 configure 来配置目标端口:
把刚才的端口 9229 填上去:
然后就可以看到 chrome 扫描到了这个 target,点击 inspect 就可以连上这个 debugger server。
之后就可以设置断点、单步执行、执行表达式、查看作用域变量等,这些功能都是通过 v8 debug protocol 来实现的。
vscode debugger
在 vscode 里面写代码,在 chrome devtools 里调试比较麻烦,vscode 也实现了 debugger 的支持,可以直接用 vscode 来调试。
使用vscode 调试能力的方式是修改项目根目录下的 .vscode/launch.json 配置。
attach
点击右下角的按钮来添加一个配置项。这里选择 nodejs 的 attach:
因为已经通过 node --inspect-brk 启动了 websocket 的 debugger server,那么只需要启动 websocket client,然后 attach 上 9229 端口就行。
点击左侧的按钮,就可以连上 debugger server 开始调试:
launch
这样先通过 node --inspect-brk 启动 debugger server,然后再添加 vscode debug 配置来连接上太麻烦了,能不能把这两步合并呢?
当然可以,只要添加一个 launch 的配置:
这里的 type 是 launch,就是启动 debgger server 并且启动一个 debugger client 连接上该 server。运行的程序是根目录下的 index2.js,还可以设置 stopOnEntry 来在首行断住。
点击调试,就可以看到能够成功的调试该 js 文件。
vscode 会启动 debugger server,然后启动 debugger client 自动连接上该 server,这些都不需要我们去关心。
这样我们就可以成功的使用 vscode debugger 来调试 nodejs 代码。
vscode debugger 进阶
debugger client 中我们最常用的还是 vscode,这里着重讲一下 vscode debugger 的各种场景下的配置。
sourcemap
如果调试 ts 代码,肯定不能调试编译后的代码,要能够映射回源码,这个是 sourcemap 做的事情。调试工具都支持 sourcemap,比如 chrome devtools 和 vscode debugger,都支持文件末尾的 sourcemap url 的解析:
//# sourceMappingURL=index.js.map
这样当调试 index.js的时候,如果它是 ts 编译的出来的,就会自动找到对应的 ts。
当然,如果调试配置里面直接指定了 ts,那么要能够调试需要再配置 outFiles,告诉 vscode 去哪里找 sourcemap。
这样,在 ts 源码中打的断点和在编译出的 js 打的断点都能生效。
多进程调试
当代码中有子进程的时候,就有了第二条控制流,需要再启动一个 debugger。
比如 vscode,它是基于 electron,需要启动一个主进程,一些渲染进程。主进程是通过 launch 启动的,而渲染进程则是后来 attach 的。
主进程启动的时候,通过 --remote-debugging-port 来指定子进程自动的时候的 debugger server 的端口。
outFiles 来指定 sourcemap 的位置,这样才可以直接调试 ts 源码。runtimeExecutable 是用 vscode 的运行时替代掉了 nodejs(一般不需要设置)。
然后渲染进程是后面启动的,我们通过参数配置了会启动在 9222 端口,那么只要 attach 到那个端口就可以调试该进程了。
vscode 支持多 target 调试,也就是可以在 vscode 里面同时启动 多个 debugger。可以切换不同的 debugger 来调试不同的进程。
总结
debugger 的使用是一项很重要的能力,对于 nodejs 水平的提升很有帮助。
nodejs debugger 的原理是 js 引擎会启动 debugger server(websocket),等待客户端连接,我们可以通过各种 debugger client 连上来进行调试,比如 chrome devtools、vscode debugger。
调试 nodejs 代码更多还是使用 vscode debugger(当然有的时候也会使用 chrome devtools 调试,基于 chrome devtools 的 memory 来进行内存分析,定位内存泄漏问题的时候很有帮助)。
vscode debugger 的使用主要是在 .vscode/launch.json 里面添加调试配置。
调试配置分为 launch 和 attach 两种:
launch 会启动 debugger server 并用 debugger client 连接上 attach 只是启动 debugger client 连接上已有的 debugger server,所以要指定端口
具体的配置项常用的有:
outFiles 指定 sourcemap 的位置,用来调试 ts 源码等需要编译的代码 stopOnEntry 在首行停住 args 来指定一些命令行参数 runtimeExecutable 当运行时不是 nodejs 的时候需要指定,比如 vscode 或者其他的一些运行时
基于这些配置我们就可以调试各种场景下的 nodejs 代码,需要编译的,或者多个进程的。
不夸张地说,如果你熟悉了 debugger 的使用,理解各种 nodejs 代码都会简单很多。希望这篇文章能够帮助大家了解 debugger 的原理,并且能够使用 chrome devtools 或者 vscode debugger 来调试 nodejs 代码。知道有 sourcemap 以及多进程的情况下都怎么调试。