一、 try-catch语句
try {
window.someNonexistentFunction();
} catch (error){
alert(error.message);
}
如果try
块中的任何代码发生了错误,就会立即退出代码执行过程,然后接着执行catch
块。此时,catch
块会接收到一个包含错误信息的对象。即使你不想使用这个错误对象,也要给它起个名字。这个对象中包含的实际信息会因浏览器而异,但共同的是有一个保存着错误消息的message
属性
finally 子句
finally
子句是可选的,但是一经使用必然执行(无论try
语句块中的代码执行正常或者执行错误跳到catch
块中都不会组织finally
子句的执行),如果提供finally
子句,则catch
子句就成了可选的(catch
或finally
有一个即可)。
function testFinally(){
try {
return 2;
} catch (error){
return 1;
} finally {
return 0;
}
}
调用这个函数会返回0
,因为最后还有一个finally
子句,结果就会导致try
语句块的return
语句被忽略;也就是说,调用这个函数只能返回0
。
错误类型
-
Error
:基类型,其他错误类型都继承自该类型。 -
EvalError
:会在使用eval()
函数而发生异常时被抛出。没有正确的调用或者给eval
属性赋值。 -
RangeError
:会在数值超出相应范围时触发。例如:指定了数组不支持的项数(如-20
或Number.MAX_VALUE
) -
ReferenceError
:引用错误,在访问不存在的变量时,就会发生这种错误。 -
SyntaxError
:语法错误,如果在定义一个函数时遗漏了闭合花括号 (}
),则触发了一个语法错误。 -
TypeError
:在变量中保存着意外的类型时,或者在访问不存在的方法时,都会导致这种错误。 -
URIError
:在使用encodeURI()
或decodeURI()
,而URI
格式不正确时,就会导致URIError
错误。
利用不同的错误类型,可以获悉更多有关异常的信息,从而有助于对错误作出恰当的处理。
try {
someFunction();
} catch (error){
if (error instanceof TypeError){
//处理类型错误
} else if (error instanceof ReferenceError){
//处理引用错误
} else {
//处理其他类型的错误
}
}
合理使用try-catch
当try-catch
语句中发生错误时,浏览器会认为错误已经被处理了,因而不会向用户报告错误。
如果你在使用一个大型JavaScript
库中的函数,该函数可能会有意无意地抛出一些错误。由于我们不能修改这个库的源代码,所以大可将对该函数的调用放在try-catch
语句当中,万一有什么错误发生,也好恰当地处理它们(无法控制的错误)。
如果传递给函数的参数是字符串而非数值,就会造成函数出错,那么就应该先检查参数的类型,然后再决定如何去做。在这种情况下,不应用使用try-catch
语句。
二、抛出错误
与try-catch
语句相配的还有一个throw
操作符,用于随时抛出自定义错误。抛出错误时,必须要给throw
操作符指定一个值,这个值是什么类型,没有要求。
throw 12345;
throw "Hello world!";
throw true;
throw { name: "JavaScript"};
throw
语句用来抛出一个用户自定义的异常。当前函数的执行将被停止(throw
之后的语句将不会执行),并且控制将被传递到调用堆栈中的第一个catch
块(此时catch
块中的error
对象就是throw
语句所抛出的)。如果调用者函数中没有catch
块,程序将会终止。
通过使用某种内置错误类型,可以更真实地模拟浏览器错误。每种错误类型的构造函数接收一个参数,即实际的错误消息。浏览器会像处理自己生成的错误一样,来处理抛出的错误。换句话说,浏览器会以常规方式报告这一错误,并且会显示这里的自定义错误消息。
throw new Error("Something bad happened.");
throw new SyntaxError("I don’t like your syntax.");
throw new TypeError("What type of variable do you take me for?");
利用原型链还可以通过继承Error
来创建自定义错误类型。
function CustomError(message){
this.name = "CustomError";
this.message = message;
}
CustomError.prototype = new Error();
throw new CustomError("My message");
浏览器对待继承自Error
的自定义错误类型,就像对待其他错误类型一样。如果要捕获自己抛出的错误并且把它与浏览器错误区别对待的话,创建自定义错误是很有用的。
抛出错误的时机
要针对函数为什么会执行失败给出更多信息,抛出自定义错误是一种很方便的方式。应该在出现某种特定的已知错误条件,导致函数无法正常执行时抛出错误。
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
参数不是数组,就会抛出一个错误。错误消息中包含了函数的名称,以及为什么会发生错误的明确描述。这样就方便了错误的定位,在开发过程中应重点关注函数和可能导致函数执行失败的因素。良好的错误处理机制应该可以确保代码中只发生你自己抛出的错误。
说到抛出错误与捕获错误,我们认为只应该捕获那些你确切地知道该如何处理的错误。捕获错误的目的在于避免浏览器以默认方式处理它们;而抛出错误的目的在于提供错误发生具体原因的消息。
三、错误事件
任何没有通过try-catch
处理的错误都会触发window
对象的error
事件,在任何Web
浏览器中,onerror
事件处理程序都不会创建event
对象,但它可以接收三个参数:错误消息、错误所在的URL
和行号。要指定onerror
事件处理程序,必须使用如下所示的DOM0
级技术,它没有遵循“DOM2 级事件”
的标准格式。
只要发生错误,无论是不是浏览器生成的,都会触发error
事件,并执行这个事件处理程序。然后,浏览器默认的机制发挥作用,像往常一样显示出错误消息。像下面这样在事件处理程序中返回false
,可以阻止浏览器报告错误的默认行为。
window.onerror = function(message, url, line){
alert(message);
return false;
};
通过返回false
,这个函数实际上就充当了整个文档中的try-catch
语句,可以捕获所有无代码处理的运行时错误。这个事件处理程序是避免浏览器报告错误的最后一道防线,理想情况下,只要可能就不应该使用它。只要能够适当地使用try-catch
语句,就不会有错误交给浏览器,也就不会触发error
事件。
图像也支持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
事件时,图像下载过程已经结束,也就是说不能再重新下载了。
四、常见的错误类型
- 类型转换错误
使用了相等和全等操作符比较了数值5
和字符串"5"
。相等操作符首先会将数值5
转换成字符串"5"
,然后再将其与另一个字符串"5"
进行比较,结果是true
。全等操作符知道要比较的是两种不同的数据类型,因而直接返回false
。使用全等和非全等操作符,可以避免发生因为使用相等和不相等操作符引发的类型转换错误。
在流控制语句中使用非布尔值,是极为常见的一个错误来源。为避免此类错误,就要做到在条件比较时切实传入布尔值。实际上,执行某种形式的比较就可以达到这个目的。
function concat(str1, str2, str3){
var result = str1 + str2;
if (str3){
//绝对不要这样!!! 因为不是只有字符串值才可以转换为true
//并不是只有undefined 才会被转换成false
result += str3;
}
// 应使用如下比较
if (typeof str3 == "string"){ //恰当的比较
result += str3;
}
return result;
}
- 数据类型错误
在使用变量和函数参数之前,不会对它们进行比较以确保它们的数据类型正确。为了保证不会发生数据类型错误,只能编写适当的数据类型检测代码。
//安全,非数组值将被忽略
function reverseSort(values){
if (values instanceof Array){
values.sort();
values.reverse();
}
}
// 不要使用下列判断检测
// if (values){} 任何非数组且能转换为true的值都将导致错误
// if (values != null){} 不等于null 只能屏蔽null 和 undefined
// if (typeof values.sort == "function"){} 只针对某个特性检测 可以定义一个拥有同名sort方法的对象
- 通信错误
JavaScript
与服务器之间的任何一次通信,都有可能会产生错误。
第一种通信错误与格式不正确的URL
或发送的数据有关。最常见的问题是在将数据发送给服务器之前,没有使用encodeURIComponent()
对查询字符串或数据进行编码。另外,在服务器响应的数据不正确时,也会发生通信错误。
五、区分致命错误和非致命错误
区分非致命错误和致命错误的主要依据,就是看它们对用户的影响。设计良好的代码,可以做到应用程序某一部分发生错误不会不必要地影响另一个实际上毫不相干的部分。
对于非致命错误,可以根据下列一或多个条件来确定,没有必要因为发生了非致命错误而打断用户:
- 不影响用户的主要任务;
- 只影响页面的一部分;
- 可以恢复;
- 重复相同操作可以消除错误。
致命错误,可以通过以下一或多个条件来确定:
- 应用程序根本无法继续运行;
- 错误明显影响到了用户的主要操作;
- 会导致其他连带错误。
在发生致命错误时,应该立即给用户发送一条消息,告诉他们无法再继续手头的事情了。假如必须刷新页面才能让应用程序正常运行,就必须通知用户,同时给用户提供一个点击即可刷新页面的按钮。
记录错误
首先需要在服务器上创建一个页面(或者一个服务器入口点),用于处理错误数据。这个页面的作用无非就是从查询字符串中取得数据,然后再将数据写入错误日志中集中保存错误日志,以便查找重要错误的原因。
function logError(sev, msg){
var img = new Image();
img.src = "log.php?sev=" + encodeURIComponent(sev) + "&msg=" +encodeURIComponent(msg);
}
for (var i=0, len=mods.length; i < len; i++){
try {
mods[i].init();
} catch (ex){
logError("nonfatal", "Module init failed: " + ex.message);
}
}
只要是使用try-catch
语句,就应该把相应错误记录到日志中。记录到服务器中的错误消息应该尽可能多地带有上下文信息,以便鉴别导致错误的真正原因。
使用了Image
对象来发送请求,这样做非常灵活,主要表现如下几方面。
- 所有浏览器都支持
Image
对象,包括那些不支持XMLHttpRequest
对象的浏览器。 - 可以避免跨域限制。通常都是一台服务器要负责处理多台服务器的错误,而这种情况下使用
XMLHttpRequest
是不行的。 - 在记录错误的过程中出问题的概率比较低。大多数
Ajax
通信都是由JavaScript
库提供的包装函数来处理的,如果库代码本身有问题,而你还在依赖该库记录错误,可想而知,错误消息是不可能得到记录的。
六、在控制台输出消息
可以通过console
对象向JavaScript
控制台中写入消息,Opera 10.5
之前的版本中,JavaScript
控制台可以通过opera.postError()
方法来访问。这个方法接受一个参数,即要写入到控制台中的参数,别看opera.postError()
方法的名字好像是只能输出错误,但实际上能通过它向JavaScript
控制台中写入任何信息。
还有一种方案是使用LiveConnect
,也就是在JavaScript
中运行Java
代码。Firefox
、Safari
和Opera
都支持LiveConnect
,因此可以操作Java
控制台。例如,通过下列代码就可以在JavaScript
中把消息写入到Java
控制台。
java.lang.System.out.println("Your message");
这个log()
函数检测了哪个JavaScript
控制台接口可用,然后使用相应的接口。可以在任何浏览器中安全地使用这个函数,不会导致任何错误
function log(message){
if (typeof console == "object"){
console.log(message);
} else if (typeof opera == "object"){
opera.postError(message);
} else if (typeof java == "object" && typeof java.lang == "object"){
java.lang.System.out.println(message);
}
}
七、常见的IE错误
- 操作终止
operation aborted
在修改尚未加载完成的页面时,就会发生操作终止错误。发生错误时,会出现一个模态对话框,告诉你“操作终止”
,单击确定(OK
)按钮,则卸载整个页面,继而显示一张空白屏幕;此时要进行调试非常困难。当<script>
节点被包含在某个元素中,而且JavaScript
代码又要使用appendChild()
、innerHTML
或其他DOM
方法修改该元素的父元素或祖先元素时,将会发生操作终止错误(因为只能修改已经加载完毕的元素)。
- 无效字符
illegal character
JavaScript
文件必须只包含特定的字符。在JavaScript
文件中存在无效字符时,IE
会抛出无效字符(invalid character
)错误。
- 未找到成员
Member not found
如果在对象被销毁之后,又给该对象赋值,就会导致未找到成员错误。
发生这个错误的最常见情形是使用event
对象的时候。IE
中的event
对象是window
的属性,该对象在事件发生时创建,在最后一个事件处理程序执行完毕后销毁。一般在事件处理程序中将event
对象赋值给一个变量var e = window.event
,事件处理程序执行完毕后,该变量就会自动被销毁,如果在事件处理程序中,有延迟函数去访问或修改该变量的属性,就会导致未找到成员错误。
- 系统无法找到指定资源
The system cannot locate the resource specified
在使用JavaScript
请求某个资源URL
,而该URL
的长度超过了IE
对URL
最长不能超过2083
个字符的限制时,就会发生这个错误。同时也限制用户在浏览器自身中使用的URL
长度(其他浏览器对URL
的限制没有这么严格)。IE
对URL
路径还有一个不能超过2048
个字符的限制。