第14章 错误处理与调试 (一)

本文详细介绍了JavaScript中的错误处理机制,包括浏览器错误报告、try-catch语句的使用、错误类型的识别与处理,以及如何通过抛出错误和使用事件来增强错误处理能力。重点阐述了如何利用自定义错误提高代码的可维护性和可读性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

14.1 浏览器错误报告

14.1.1  Internet Explorer

如果错误发生在位于外部文件的脚本中,行号通常会与错误所在的行号差 1 。如果是嵌入在页面中的脚本发生错误,则行号就是错误所在的行号。

14.2 错误处理

14.2.1 try-catch 语句

ECMA-262 第3版引入了 try-catch 语句,作为 JavaScript 中处理异常的一种标准方式。基本的语法如下所示,显而易见,这与 Java 中的 try-catch 语句是完全相同的:

try {

// 可能导致错误的代码

} catch(error) {

// 错误发生时怎么处理

}

也就是说,我们应该把所有可能会抛出错误的代码都放在 try 语句中,而把那些用于错误处理的代码放在 catch 块中。例如:

try {

window.someNonexistentFunction();                      // 调用不存在的函数

} catch (error) {

alert("An error happened!");

}

如果 try 块中的任何代码发生了错误,就会立即退出代码执行过程,然后接着执行 catch 块。此时,catch 块会接收到一个包含错误信息的对象。与在其他语音中不同的是,即使你不想使用这个错误对象,也要给它起个名字。这个对象中包含的实际信息会因浏览器而异,但共同的是有一个保存着错误消息的 message 属性。ECMA-262 还规定了一个保存错误类型的 name 属性;当前所有浏览器都支持这个属性 (Opera 9 之前的版本不支持这个属性)。因此,在发生错误时,就可以像下面这样实事求是地显示浏览器给出的消息:

try {

window.someNonexistentFunction();                               // 调用不存在的函数

} catch (error) {

alert(error.message);

}

这个例子在向用户显示错误消息时,使用了错误对象的 message 属性。这个 message 属性是唯一一个能够保证所有浏览器都支持的属性,除此之外,IE、Firefox、Safari、Chrome 以及 Opera 都为事件对象添加了其他相关信息。IE 添加了与 message 属性完全相同的 description 属性,还添加了保存着内部错误数量的 number 属性。

1.finally 子句

虽然在 try-catch 语句中是可选的,但 finally 子句一经使用,其代码无论如何都会执行。换句话说,try 语句块中的代码全部正常执行,finally 子句会执行;如果因为出错执行了 catch 语句块,finally 子句照样还会执行。只要代码中包含 finally 子句,则无论 try 或 catch 语句块中包含什么代码 -- 甚至 return 语句,都不会阻止 finally 子句的执行。来看下面这个函数:

function testFinally(){

try {

return 2;

} catch (error){

return 1;

} finally {

return 0;

}

}

这个函数在 try-catch 语句的每一部分都放了一条 return 语句。表面上看,调用这个函数会返回 2 ,因为返回 2 的 return 语句位于 try 语句块中,而执行该语句又不会出错。可是,由于最后还有一个 finally 子句,结果就会导致该 return 语句被忽略;也就是说,调用这个函数只能返回 0 。如果把 finally 子句拿掉,这个函数将返回 2 。

请读者务必要记住,只要代码中包含 finally 子句,那么无论 try 还是 catch 语句块中的 return 语句都将被忽略。因此,在使用 finally 子句之前,一定要非常清楚你想让代码怎么样。

2.错误类型

执行代码期间可能会发生的错误有多种类型。每种错误都有对应的错误类型,而当错误发生时,就会抛出相应类型的错误对象。ECMA-262定义了下列7种错误类型:

  • Error
  • EvalError
  • RangeErro
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError
其中,Error 是基类型,其他错误类型都继承自该类型。因此,所有错误类型共享了一组相同的属性 (错误对象中的方法全是默认的对象方法)。Error 类型的错误很少见,如果有也是浏览器抛出的;这个基类型的主要目的是供开发人员抛出自定义错误。
EvalError 类型的错误会在使用 eval() 函数而发生异常时被抛出。ECMA-262 中对这个错误有如下描述,"表示全局函数 eval 的使用方式与其定义不相符"。除此之外,并没有就到底什么情况会引发这种错误给出说明。在实际开发中碰到这种错误的可能性并不大。
RangeError 类型的错误会在数值超出相应范围时触发。例如,在定义数组时,如果指定了数组不支持的项数 (如 -20 或 Number.MAX_VALUE),就会触发这种错误。下面是具体的例子:
var item1 = new Array(-20);                                             // 抛出 RangeError
var item2 = new Array(Number.MAX_VALUE);            // 抛出 RangeError
JavaScript 中经常会出现这种范围错误。
在找不到对象的情况下,会发生 ReferenceError (这种情况下,会直接导致人所共知的 "object expected" 浏览器错误)。通常,在访问不存在的变量时,就会发生这种错误,例如:
var obje = x;                                        // 在 x 并未声明的情况下抛出 ReferenceError
至于 SyntaxError ,我们把语法错误的 JavaScript 字符串传入 eval() 函数时,就会导致此类错误。例如:
eval("a++b");                                      // 抛出 SyntaxError
如果语法错误的代码出现在 eval() 函数之外,则不太可能发生  SyntaxError,因为此时的语法错误会导致 JavaScript 代码立即停止执行。
TypeError 类型在 JavaScript 中会经常用到,在变量中保存着意外的类型时,或者在访问不存在的方法时,都会导致这种错误。错误的原因虽然多种多样,但归根结底还是由于在执行特定于类型的操作时,变量的类型不符合要求所致。下面来看几个例子:
var o = new 10;                                                         // 抛出 TypeError
alert("name" in true);                                               // 抛出 TypeError
Function.prototype.toString.call("name");            // 抛出 TypeError

最常发生类型错误的情况,就是传递给函数的参数事先未经检查,结果传入类型与预期类型不相符。

在使用 encodeURI() 或 decodeURI(),而 URI 格式不正确时,就是导致 URIError 错误。这种错误也很少见,因为前面说的这两个函数的容错性非常高。

利用不同的错误类型,可以获悉更多有关异常的信息,从而有助于对错误作出恰当的处理。要想知道错误的类型,可以像下面这样在 try-catch 语句的 catch 语句中使用 instanceof 操作符

try {

someFunction();

} catch(error) {

if (error instanceof TypeError) {

// 处理类型错误

} else if (error instanceof ReferenceError) {

// 处理引用错误

} else {

// 处理其他类型的错误

}

}

在跨浏览器编程中,检查错误类型是确定处理方式的最简便途径;包含在 message 属性中的错误消息会因浏览器而异。

3.善用 try-catch

当 try-catch 语句中发生错误时,浏览器会认为错误已经被处理了,因而不会通过本章前面讨论的机制记录或报告错误。对于那些不要求用户懂技术,也不需要用户理解错误的 Web 应用程序,这应该说是个理想的结果。不过,try-catch 能够让我们实现自己的错误处理机制。

使用 try-catch 最适合处理那些我们无法控制的错误。假设你在使用一个大型 JavaScript 库中的函数,该函数可能会有意无意地抛出一些错误。由于我们不能修改这个库的源代码,所以大可将对该函数的调用放在 try-catch 语句当中,万一有什么错误发生,也好恰当地处理它们。

在明明白白地知道自己的代码会发生错误时,再使用 try-catch 语句就不太合适了。例如,如果传递给函数的参数是字符串而非数值,就会造成函数出错,那么就应该先检查参数的类型,然后再决定如何去做。在这种情况下,不应该使用 try-catch 语句。

14.2.2 抛出错误

与 try-catch 语句相配的还有一个 throw 操作符,用于随时抛出自定义错误。抛出错误时,必须要给 throw 操作符指定一个值,这个值是什么类型,没有要求。下列代码都是有效的:

throw 12345;

throw "Hello world";

throw true;

throw { name: "JavaScript"};

在遇到 throw 操作符,代码会立即停止执行。仅当有 try-catch 语句捕获到被抛出的值时,代码才会继续执行。

通过使用某种内置错误类型,可以更真实地模拟浏览器错误。每种错误类型的构造函数接受一个参数,即实际的错误消息。下面是一个例子:

throw new Error("Something bad happend.");

这行代码抛出了一个通用错误,带有一条自定义错误消息。浏览器会像处理自己生成的错误一样,来处理这行代码抛出的错误。换句话说,浏览器会以常规方式报告这一错误,并且会显示这里的自定义错误消息。像下面使用其他错误类型,也可以模拟出类似的浏览器错误;

throw new SyntaxError("I don't like your syntax.");

throw new TypeError("What type of variable do you take me for?");

throw new RangeError("Sorry, you just don't have the range.");

throw new EvalError("That doesn't evaluate.");

throw new URIError("Uri, is that you?");

throw new ReferenceError("You didn't cite your references properly");

在创建自定义错误消息时最常用的错误类型是 Error、RangeError、ReferenceError 和 TypeError 。

另外,利用原型链还可以通过继承 Error 来创建自定义错误类型。此时,需要为新创建的错误类型指定 name 和 message 属性。来看一个例子:

function CustomError(message){

this.name = "CustomError";

this.message = message;

}

CustomError.prototype = new Error();

throw new CustomError("My message");

浏览器对待继承自 Error 的自定义错误类型,就像对待其他错误类型一样。如果要捕获自己抛出的错误并且把它与浏览器错误区别对待的话,创建自定义错误是很有用的。

IE 只有在抛出 Error 对象的时候才会显示自定义错误消息。对于其他类型,它都无一例外地显示 "exception thrown and not caught" (抛出了异常,且未被捕获)。

1.抛出错误的时机

要针对函数为什么会执行失败给出更多信息,抛出自定义错误是一种很方便的方式。应该在出现某种特定的已知错误条件,导致函数无法正常执行时抛出错误。换句话说,浏览器会在某种特定的条件下执行函数时抛出错误。例如,下面的函数会在参数不是数组的情况下失败:

function process(values){

values.sort();

for(var i=0, len=values.length; i<len; i++){

if(values[i] > 100){

return values[i];

}

}

return -1;

}

如果执行这个函数时传给它一个字符串参数,那么对 sort() 的调用就会失败。对此,不同浏览器会给出不同的错误消息,但都不是特别明确,如下所示:

  • IE: 属性或方法不存在;
  • Firefox: values.sort() 不是函数;
  • Safari: 值 undefined (表达式 values.sort 的结果) 不是对象;
  • Chrome: 对象名没有方法 'sort'。
  • Opera: 类型不匹配 (通常是在需要对象的地方使用了非对象值)。
尽管 Firefox 、Chrome 和 Safari 都明确指出了代码中导致错误的部分,但错误消息并没有清楚地告诉我们到底出了什么问题,该怎么修复问题。在处理类似前面例子中的那个函数时,通过调试处理这些错误消息没有什么困难。但是,在面对包含数千行 JavaScript 代码的复杂的 Web 应用程序时,要想查找错误来源就没有那么容易了。这种情况下,带有适当信息的自定义错误能够显著提升代码的可维护性。来看下面的例子:
function process(values){
if(!(values instanceof Array)){
throw new Error("process(): Argument must be an array.");
}
values.sort();
for (var i=0, len=values.length; i<len; i++){
if(values[i] > 100){
return values[i];
}
}
return -1;
}
在重写后的这个函数中,如果 values 参数不是数组,就会抛出一个错误。错误消息中包含了函数的名称,以及为什么会发生错误的明确描述。如果一个复杂的 Web 应用程序发生了这个错误,那么查找问题的根源也就容易多了。
建议读者在开发 JavaScript 代码的过程中,重点关注函数和可能导致函数执行失败的因素。良好的错误处理机制应该可以确保代码中只发生你自己抛出的错误。
2.抛出错误与使用 try-catch
关于何时该抛出错误,而何时该使用 try-catch 来捕获它们,是一个老生常谈的问题。一般来说,应用程序架构的较低层次中经常会抛出错误,但这个层次并不会影响当前执行的代码,因而错误通常得不到真正的处理。如果你打算编写一个要在很多应用程序中使用的 JavaScript 库,甚至只编写一个可能会在应用程序内部多个地方使用的辅助函数,我们都强烈建议你在抛出错误时提供详尽的信息。然后,即可在应用程序中捕获并适当地处理这些错误。
说到抛出错误与捕获错误,我们认为只应该捕获那些你确切地知道该如何处理的错误。捕获错误的目的在于避免浏览器以默认方式处理它们;而抛出错误的目的在于提供错误发生具体原因的消息。

14.2.3 错误 (error) 事件

任何没有通过 try-catch 处理的错误都会触发 window 对象的 error 事件。这个事件是 Web 浏览器最早支持的事件之一,IE 和 Firefox 为保存向后兼容,并没有对这个事件作任何修改 (Opera、Chrome 和 Safari 不支持 error 事件)。在任何 Web 浏览器中,onerror 事件处理程序都不会创建 event 对象,但它可以接受 3 个参数: 错误消息、错误所在的 URL 和行号。多数情况下,只有错误消息有用,因为 URL 只是给出了文档的位置,而行号所指的代码行既可能出自嵌入的 JavaScript 代码,也可能出自外部的文件。要指定 onerror 事件处理程序,必须使用如下所示的 DOM0 级技术,它没有遵循 “DOM2级事件” 的标准格式:
window.onerror = function(message, url, line){
alert(message);
};
只要发生错误,无论是不是浏览器生成的,都会触发 error 事件,并执行这个事件处理程序。然后,浏览器默认的机制发挥作用,像往常一样显示出错误消息。像下面这样在事件处理程序中返回 false ,可以阻止浏览器报告错误的默认行为
window.onerror = function(message, url, line){
alert(message);
return false;
};
通过返回 false,这个函数实际上就充当了整个文档中的 try-catch 语句,可以捕获所有无代码处理的运行时错误。这个事件处理程序是避免浏览器报告错误的最后一道防线,理想情况下,只要可能就不应该使用它。只要能够适当地使用 try-catch 语句,就不会有错误交给浏览器,也就不会触发 error 事件。
浏览器在使用这个事件处理错误时的方式有明显不同。在 IE 中,即使发生 error 事件,代码仍然会正常执行;所有变量和数据都将得到保留,因此能在 onerror 事件处理程序中访问它们。但在 Firefox 中,常规代码会停止执行,事件发生之前的所有变量和数据都将被销毁,因此就无法判断错误了。
图像也支持 error 事件。只要图像的 src 特性中的 url 不能返回可以被识别的图像格式,就会触发 error 事件。此时的 error 事件遵循 DOM 格式,会返回一个以图像为目标的 event 对象。下面是一个例子:
var image = new Image();
EventUtil.addHandler(image, "load", function(event){
alert("Image loaded!");
});
EventUtil.addHandler(image, "error", function(event){
alert("Image not loaded!");
});
image.src = "smilex.gif";                   // 指定不存在的文件
在这个例子中,当加载图像失败时就会显示一个警告框。需要注意的是,发生 error 事件时,图像下载过程已经结束,也就是说不能再重新下载了。
在 Firefox 3.0 之前的版本中,不会触发图像的 error 事件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值