第15章 除错和调试
每个人都会犯错误,但是在JavaScript中犯错可能会导致程序无法正确运行,或者根本就不运行。当我们初次开始使用JavaScript的时候,可能会犯很多的错误。试图搞清楚为什么一个脚本没有按照应该的方式工作,这可能令人感到沮丧,但是,它也是编程的一部分。幸运的是,通过一些经验和实践,我们将能够搞清楚为什么会发生错误以及如何修正错误。
本章介绍一些最常见的编程错误,并且,更重要的是,教你如何识别脚本中的错误,用程序员的话来说,就是调试错误。此外,本章中的教程还将带你一步一步地调试一个带有错误的脚本。
15.1 常见JavaScript编程错误
程序出错的方式有无数种,从简单的输入错误到可能不时会出现的更为隐秘的错误。然而,还是有很多错误经常会困扰初级的(甚至是高级的)JavaScript程序员。浏览本节给出的列表,并且在编程的时候记住它们。你可能会发现,知道这些常见的错误会使得更容易识别和修正自己的程序中的错误。
15.1.1 没有结束符号
你可能已经注意到了,JavaScript中充满无数的圆括号、花括号、分号、引号和其他的标点符号。由于计算机严格的本性,漏掉一个单个的标点符号可能导致程序陷入绝境。最为常见的错误之一是仅仅忘掉了包含结束的标点符号。例如,alert('hello';将会导致一个错误,因为结束的标点符号漏掉了,正确形式是alert('hello');。
漏掉了结束的标点符号可能引起一个语法错误(参见本小节“快速熟悉:错误类型”部分的介绍)。这种“语法错误”使得脚本根本无法运行。当我们测试脚本运行的时候,浏览器允许我们知道是否犯了语法错误;但是,令人混淆的是,它们对问题的描述也不同。在Firefox错误控制台中(参见1.6.1节),我们得到“missing)after argument list”这样的错误消息;而Internet Explorer(参见1.6.2节)则把这个错误报告为“Expected')’”;而Safari错误控制台(参见1.6.3节)则给出一条没有多大帮助的消息“SyntaxError:ParseError.”。正如本书1.6节开始处所介绍,Firefox常常提供最容易理解的错误消息,因此,在试图搞清楚脚本为什么不工作的时候,最好从使用该浏览器开始(如图15-1所示)。
图 15-1 Firefox的错误控制台列出了浏览器遇到的所有JavaScript错误。我们可以在Windows上选择WebDeveloper→Error Console(Ctrl+Shift+J),或者在Macs上选择Tools→WebDeveloper→Error Console(æ-Shift-J)来显示控制台。既然控制台列出了它在所有页面上遇到的错误,我们希望经常通过单击Clear按钮(如图中圆圈所示)来清除列表
alert('hello';中的语法错误确实容易找到。然而,当我们有了一个嵌套的圆括号的时候,很容易漏掉结束的圆括号并且很难一眼就看到错误。例如:
if((x>0)&&(y<10){
//do something
}
在这个例子中,条件语句最终的结束圆括号漏掉了,也就是紧接着(y<10)后面的一个圆括号。第1行代码应该是if((x>0)and(y<10)){。另外,Firefox提供了对问题的最为清晰的说明:“missing)after condition.”。表15-1给出了Firefox的错误控制台语法错误消息的列表。
当我们忘了包含第2个引号的时候,也会遇到一个语法错误。例如,alert('hello);会产生一个错误,因为最终的引号漏掉了,应该是alert('hello');。在Firefox中,如果忘记包含两个引号,将会得到一个“unterminated string literal”错误,而Internet Explorer则报告一个“unterminated string constant”错误;Safari再次产生没有多大用处的错误消息:“SyntaxError:Parse Error”。
花括号也是成对出现的,并且,我们将在条件语句中使用它们(参见3.1.1节),在循环中(参见3.3节)中使用它们,当创建JavaScript对象直接量的时候(参见4.8.3节)以及创建JSON(参见11.4节)的时候使用它们:
if(score==0){
alert('game over');
在这个例子中,结束的花括号漏掉了,并且脚本会产生一个语法错误。
避免漏掉结束的标点符号的一种方法是,总是在添加其他程序之前添加结合的标点符号。例如,假设想要最终得到如下的代码:
if((name=='bob')&&(score==0)){
alert('You lose(but at least you have a great name)');
}
首先是输入外围的两个元素,为条件创建一个基本的框架,如下所示:
if(){}
此时,还没有太多代码,但是,很容易看到是否错误地遗漏了任何标点。接下来,添加更多的代码,一点一点地添加,直到程序全都编写完毕。在创建一个复杂的JavaScript对象直接量的时候,例如,用来设置Validation插件的对象直接量(参见本书9.4节);或者创建一个如11.4节所讨论的JSON对象的时候,也可以使用这种方法。从基本的结构开始:
var options={
};
然后,添加更多的结构:
var options={
rules:{
},
messages:{
}
};
最终的对象是:
var options={
rules:{
name:'required',
email:'email'
},
messages:{
name:'Please type your name',
email:'Please type your e-mail address.’}
};
这种方法允许我们通过几个步骤来检查自己的工作,并且使得识别标点符号方面的任何错误容易了很多。
表15-1:Firefox的错误控制台(参见1.6.1节的介绍)对于语法错误的消息给出了最为清晰的描述。当脚本不工作的时候,在Firefox中预览并查看错误控制台。这里给出一些最为常见的错误消息及其含义
快速熟悉错误类型
在编写JavaScript程序的时候,会遇到3种基本的错误类型。一些这样的错误直接而明显,而其他的一些错误总是在脚本完成并运行的时候才会露出它们丑陋的苗头。
语法错误。语法错误基本上是使得JavaScript解释器举手投降并说“我放弃”的错误。任何涉及漏掉结束的圆括号、花括号或引号的错误都会产生语法错误。在Web浏览器读取脚本的时候,会立即遇到语法错误,因此,脚本没有机会运行。语法错误的错误消息总是出现在Web浏览器的错误控制台中。
运行时错误。当浏览器成功地读取一段脚本的代码并且JavaScript解释器解释它之后,它可能仍然会遇到错误。即便程序的语法是正确的,在程序运行的时候,其他的问题可能会出现,这些问题叫做运行时错误。例如,假设在脚本开始处定义了一个名为message的变量,在脚本的后面,我们给图像添加了一个单击函数,以便在单击该图像的时候出现一个警告框。假设这个例子的警告代码如下所示:alert(MESSAGE);。这条语句的语法没有问题,但是它调用了变量MESSAGE而不是小写的message。正如本书15.1.4节所提到的,JavaScript是区分大小写的,因此,MESSAGE和message引用两个不同的变量。当访问者单击图像的时候,JavaScript解释器查找变量MESSAGE(这个变量并不存在)并且产生一个运行时错误。
当试图访问页面上不存在的一个元素或者一个浏览器还没有读入到其内存中的一个元素的时候,会产生另一种常见的运行时错误。参见5.4.1节对于jQuery的$(document).ready()函数的介绍,了解关于这一问题的更多细节。
逻辑错误。有时候,即便脚本看上去运行了,它也不会产生我们想要的结果。例如,我们可能有一条if/else语句(参见3.1.1节),当一个条件为true的时候执行步骤A,而当条件为false的时候执行步骤B。遗憾的是,程序不会到达步骤B,即便我们确定条件为false。当我们不正确地使用相等操作符的时候(参见2.5.1节),可能会发生这种错误。从JavaScript解释器的观点来看,一切都是技术错误,但是,我们已经在程序的逻辑中犯了一个错误,这导致脚本不能按计划执行。
逻辑错误的另一个例子是无限循环,这是永远运行的一段代码,通常会引起程序挂起并且有时候甚至会导致Web浏览器崩溃。下面是无限循环的一个例子:
for(var i=1;i>0;i++){
//this will run forever
}
简而言之,只要测试条件(i>0)为true,这个循环就将运行。由于i从一个值1开始(var i=1),并且每次它迭代循环的时候,i都会增加1(i++),i的值将总是大于1。换句话说,循环不会停止(如果需要回顾关于for循环的知识,请参见3.3.3节)。
逻辑错误属于最难发现的错误。然而,使用15.2节所介绍的调试技术,我们应该能够发现所会遇到的任何问题。