当前位置:嗨网首页>书籍在线阅读

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个对象的值。其中两个—— testsecond ——已经有了实际的值,而另外两个的值是 <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 都可以直接终止程序的运行。