03-Node调试器
11.1.1 Node调试器
只要可以,我总会使用原生的实现,而不是用第三方工具。幸运的是,对于调试需求,Node提供了内置的调试支持。它并不复杂,且十分实用。
你可以使用 debugger
命令直接向代码中插入断点:
for (var i = 0; i <= test; i++) {
debugger;
second+=i;
}
启动程序时加上 debug
参数,就可以开始调试程序了:
node debug application
我创建了一个嵌入了两个 debugger
断点的程序,来演示调试器如何使用:
var fs = require('fs');
var concat = require('./external.js').concatArray;
var test = 10;
var second = 'test';
for (var i = 0; i <= test; i++) {
debugger;
second+=i;
}
setTimeout(function() {
debugger;
test = 1000;
console.log(second);
}, 1000);
fs.readFile('./log.txt', 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
var arry = ['apple','orange','strawberry'];
var arry2 = concat(data,arry);
console.log(arry2);
});
使用下面的命令启动程序:
node debug debugtest
如果你用 --debug
命令行参数来启动Node程序,那么也可以通过 pid
连接到这个进程,进行调试(debug):
node debug -p 3383
当然也可以通过URI连接到运行中的进程进行调试:
node debug http://localhost:3000
调试器运行起来之后,程序会停在第一行,并且列出最前面的几行代码:
< Debugger listening on port 5858
debug> . ok
break in debugtest.js:1
> 1 var fs = require('fs');
2 var concat = require('./external.js').concatArray;
3
你可以使用 list
命令来列出上下文中的源代码。比如, list(10)
会列出前面10行代码和后面10行代码。在本例中,输入 list(25)
则会将程序中的所有代码以及行号都打印出来。你可以使用 setBreakpoint
命令来添加额外的断点,也可以使用它的快捷命令: sb
。让我们给测试程序的第19行设置一个断点,也就是在 fs.readFile()
方法的回调函数中设置断点。同时我们直接在自定义模块的第三行设置一个断点:
debug> sb(19)
debug> sb('external.js',3)
你会看到一个提示说external.js尚未加载,但是这并不影响调试功能。
你也可以使用 watch('expression')
命令,在变量或者表达式上设置监视器。我们会监视 test
变量和 second
变量,以及 data
参数和 arry2
数组:
debug> watch('test');
debug> watch('second');
debug> watch('data');
debug> watch('arry2');
现在,万事俱备,只欠“调试”了。输入 cont
或者 c
,可以让程序执行到第一个断点。在输出信息中,可以看到程序已经运行到第一个断点,也可以看到所监视的那4个对象的值。其中两个—— test
和 second
——已经有了实际的值,而另外两个的值是 <error>
。这是因为程序目前并没有进入这个参数( data
)和变量( arry2
)所定义的函数的作用域中。忽略这个错误,继续调试。
debug> c
break in debugtest.js:8
Watchers:
0: test = 10
1: second = "test"
2: data = "<error>"
3: arry2 = "<error>"
6
7 for (var i = 0; i <= test; i++) {
> 8 debugger;
9 second+=i;
10 }
还有一些不常用的命令,你可以在运行到下一个断点之前尝试一下。 scripts
命令会列出已经被加载的脚本:
debug> scripts
* 57: debugtest.js
58: external.js
debug>
而 version
命令会打印出V8这个版本号。下面我们再次输入 c
来运行至下一个断点:
debug> c
break in debugtest.js:8
Watchers:
0: test = 10
1: second = "test0"
2: data = "<error>"
3: arry2 = "<error>"
6
7 for (var i = 0; i <= test; i++) {
> 8 debugger;
9 second+=i;
10 }
请注意, second
变量的值已经发生了变化。这是因为 debugger
所在的 for
循环中, second
的值被改变了。输入几次 c
命令,循环就会执行几次,同时 second
变量的值发生了连续的变化。不幸的是,用 debugger
语句生成的断点无法删除,但是使用 setBreakpoint
或者 sb
生成的断点是可以删除的。调用 clearBreakpoint
或者 cb
时,需要提供断点的脚本名称和行号。
cb('debugtest.js',19)
监视器也可以通过 unwatch
来关闭:
debug> unwatch('second')
直接调用 sb
而不输入任何参数,这会在当前行设置断点:
debug> sb();
在程序中,调试器会一直运行程序,直到遇到我们在 fs.readFile( )
的回调函数中设置的断点。这时你会发现, data
参数的值发生了变化:
debug> c
break in debugtest.js:19
Watchers:
0: test = 10
1: second = "test012345678910"
2: data = "test"
3: arry2 = undefined
17
18 fs.readFile('./log.txt', 'utf8', function (err,data) {
>19 if (err) {
20 return console.log(err)
21 }
此时 arry2
的值不再是一个错误,而是 undefined
。
我们需要一行一行地运行代码,所以就不能使用 c
命令了,而需要使用 next
或者 n
命令。当执行到第23行时,调试器会加载外部的模块,并且停留在第三行,因为我们之前对这个模块设置了断点:
debug> n
break in external.js:3
Watchers:
0: test = "<error>"
1: second = "<error>"
2: data = "<error>"
3: arry2 = "<error>"
1
2 var concatArray = function(str, arry) {
> 3 return arry.map(function(element) {
4 return str + ' ' + element;
5 });
我们可以直接跳过函数的执行,来到程序的第23行,也可以使用 stop
或者 s
命令来进入模块函数的内部:
debug> s
break in external.js:3
Watchers:
0: test = "<error>"
1: second = "<error>"
2: arry2 = "<error>"
3: data = "<error>"
1
2 var concatArray = function(str, arry) {
> 3 return arry.map(function(element) {
4 return str + ‘’ + element;
5 });
请注意,所有被监视的变量,现在都显示了一个错误信息。此时,我们已经跳出了父程序的执行上下文。在函数或者外部模块执行期间重新添加监视器,就可以避免这样的错误出现。或者,如果同一个变量在程序运行上下文和外部模块中都有定义,那么也不会出现这个错误。
**继续执行命令的bug** 如果你在程序执行结束之后输入 `c` 或者 `cont` ,调试器就会卡住,而且无法恢复。这是一个已知的bug。
backtrace
或者 bt
命令提供了一个当前执行上下文的回溯(backtrace)。当前调试状态下的返回值会被显示为代码块:
debug> bt
#0 concatArray external.js:3:3
#1 debugtest.js:23:15
我们看到两行内容,第一行是我们在加载的模块中所处的位置,第二行是我们目前在程序中所处的位置。
使用 out
或者 o
命令,可以帮助我们跳过外部函数,或者回到程序主文件。这个命令会在函数执行过程中帮助你回到主程序中(不论这个函数是在主文件中还是在一个外部模块中)。
Node调试器是基于REPL的,我们也确实可以在调试器中使用 repl
来调出REPL。如果要停止正在执行脚本,可以使用 kill
命令,而使用 restart
命令则可以重启脚本。但是要注意,重启脚本会清空所有的断点和监视器。
既然调试器是运行是在REPL中的,那么按下Ctrl-C键或者输入 .exit
都可以直接终止程序的运行。