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
最常发生类型错误的情况,就是传递给函数的参数事先未经检查,结果传入类型与预期类型不相符。
在使用 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: 类型不匹配 (通常是在需要对象的地方使用了非对象值)。