JavaScript异常处理
1. try-catch 语句
ECMA-262 第 3 版引入了try-catch
语句,作为 JavaScript 中处理异常的一种标准方式。基本的语法如下所示:
1
2
3
4
5
try {
//可能会发生错误的代码
} catch (error) {
//在错误发生时怎么处理
}
也就是说,我们应该把所有可能会抛出错误的代码都放在 try 语句块中,而把那些用于错误处理的代码放在 catch 块中。例如:
1
2
3
4
5
try {
Array.someNoneExistentFunction();
} catch (error) {
console.log('An error happened!');
}
如果 try 块中的任何代码发生了错误,就会立即退出代码执行过程。所以下例中的two()
不会被执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function one() {
console.log(noneExistVariable);
}
function two() {
console.log('function two() executed successfully.');
}
try {
one();
two();
} catch (exception) {
console.log(exception.message);
}
try 块推出代码执行后,接着执行 catch 块。此时,catch 块会接收到一个包含错误信息的对象。即使你不想使用这个错误对象,也要给它起个名字。这个对象包含的实际信息会因浏览器二异,但共同的是有一个保存着错误消息的message
属性。ECMA-262 还规定了一个保存错误的name
属性;当前所有浏览器都支持这个属性。
1.1 finally 子句
虽然在 try-catch 语句中是可选的,但 finally 子句一旦使用,其代码无论如何都会执行(不管 try 语句中的代码有没有出错,finally 都会执行)。只要代码中包含 finally 子句,则无论 try 或 catch 语句块中包含什么代码——甚至是 return 语句,都不会阻止 finally 子句的执行。
1
2
3
4
5
6
7
8
9
10
function testFinally() {
try {
return 2;
} catch (error) {
return 1;
} finally {
return 0;
}
}
console.log(testFinally());
这个函数永远会返回 0。
如果提供 finally 子句,则 catch 子句就成了可选的(catch 或 finally 有一个即可)
1.2 错误类型
执行代码期间可能会发生的错误有很多种类型。每种错误都有对应的错误类型,而当错误发生时,就会抛出相应类型的错误对象。ECMA-262 定义了下列 7 种错误类型:
Error
EvalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError
其中 Error 是基类型,其他错误类型都继承自该类型。因此,所有错误类型共享了一组相同的属性(错误对象中的方法全是默认的对象方法)。Error 类型的错误很少见,如果有也是浏览器抛出的;这个基类型的主要目的是供开发人员抛出自定义错误的。
EvalError 类型的错误会在使用 eval()函数而发生异常时抛出。如果没有把 eval()当成函数使用,就会抛出该错误:
1
2
3
4
5
6
7
8
9
10
11
12
13
//##skipEval
try {
new eval();
} catch (error) {
console.log(error.name + ':' + error.message);
}
try {
var foo;
eval = foo;
} catch (error) {
console.log(error.name + ':' + error.message);
}
在实践中,浏览器不一定会应该抛出错误时就抛出 EvalError。例如 Firfox4+, IE8 和 Safari 对一种情况会抛出 TypeError,而第二种情况会成功执行,不抛出错误。
RangeError 类型的错误会在数值超出相应范围时触发。例如,在定义数组时,如果指定了数组不支持的项数(如-20 或 Number.MAX_VALUE),就会触发这种错误:
1
2
3
4
5
6
7
8
9
10
11
try {
var items1 = new Array(-20);
} catch (error) {
console.log(error.name + ':' + error.message);
}
try {
var items2 = new Array(Number.MAX_VALUE);
} catch (error) {
console.log(error.name + ':' + error.message);
}
ReferenceError 会在找不到对象的情况下触发。
1
2
3
4
5
try {
var obj = x;
} catch (error) {
console.log(error.name + ':' + error.message);
}
SyntaxError 会在我们把错误的 JavaScript 字符串传入 eval()函数时产生。
1
2
3
4
5
try {
eval('a ++ b');
} catch (error) {
console.log(error.name + ':' + error.message);
}
如果语法错误的代码出现在 eval()函数之外,则不太可能使用 SyntaxError,因为此时的语法错误会导致 JavaScript 代码立即停止执行。
TypeError 类型在 JavaScript 中经常用到,在变量中保存着意外的类型时,或者访问不存在等方法时,都会导致这种错误。错误的原因很多,但归根结底还是由于在执行特定类型的操作时,变量的类型并不符合要求所致。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try {
var o = new 10();
} catch (error) {
console.log(error.name + ':' + error.message);
}
try {
alert('name' in true);
} catch (error) {
console.log(error.name + ':' + error.message);
}
try {
Function.prototype.toString.call('name');
} catch (error) {
console.log(error.name + ':' + error.message);
}
URIError 会在使用encodeURI()
或decodeURI()
,而 URI 格式不正确时触发。
利用不同的错误类型,可以获悉更多有关异常的信息,从而有助于对错误作出恰当的处理。要想知道错误的类型,可以像下面这样在 try-catch 中使用 instanceof 操作符。
1
2
3
4
5
6
7
8
9
10
11
try {
someFunction();
} catch (error) {
if (error instanceof TypeError) {
//处理类型错误
} else if (error instanceof ReferenceError) {
//处理引用错误
} else {
//处理其他类型的错误
}
}
在跨浏览器编程中,检查错误类型是确定处理方式的最简便途径;包含在 message 属性中的错误消息会因浏览器而异。
1.3 合理使用 try-catch
当 try-catch 语句中发生错误时,浏览器会认为错误已经被处理了。使用 try-catch 最适合处理那些我们无法控制的错误。假设你在使用一个大型 JavaScript 库中的函数,该函数可能会有意无意地抛出一些错误。由于我们不能修改这个库的的源代码,所以大可将对该函数的调用放在 try-catch 中,万一有错误发生,也好恰当地处理它们。
在明明白白地知道自己的代码会发生错误时,再使用 try-catch 语句就不合适了。例如,如果传递给函数的参数是字符串而非数值,就会造成函数出错,那么就应该先检查参数的类型,然后再决定如何去坐。在这种情况下,不应该使用 try-catch 语句。
2. 抛出错误
与 try-catch 语句相配的还有一个throw
操作符,用于随时抛出自定义错误。抛出错误时,必须给 throw 操作符指定一个值,这个值是什么类型,没有要求。
1
2
3
4
5
//##skipEval
throw 12345;
throw 'Hello world!';
throw true;
throw { name: 'JavaScript' };
在遇到 throw 操作符时,代码会立即执行。仅当有 try-catch 语句捕获到被抛出的值时,代码才会继续执行。
通过使用某种内置的错误类型,可以更真实地模拟浏览器错误。每种错误的构造函数接收一个参数,即实际的错误消息:
1
2
3
4
5
try {
throw new Error('Something bad happened.');
} catch (error) {
console.log(error.name + ':' + error.message);
}
另外利用原型链还可以通过继承 Error 来创建自定义错误类型。此时,需要为新创建的错误类型指定 name 和 message 属性。
1
2
3
4
5
6
7
8
9
10
11
12
function CustomError(message) {
this.name = 'CustomError';
this.message = message;
}
CustomError.prototype = new Error();
try {
throw new CustomError('My message');
} catch (error) {
console.log(error.name + ':' + error.message);
}
浏览器对待继承自 Error 的自定义错误类型,就像对待其他错误一样。如果要捕获自己抛出的错误并且把它与浏览器区别对待的话,创建自定义错误时很有用的。
应该在出现某种特定的已知错误条件,导致函数无法正常执行时抛出错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function process(values) {
if (!value 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] > 1000) {
return values[i];
}
}
return -1;
}
try {
process('');
} catch (error) {
console.log(error.name + ':' + error.message);
}
上面的函数中,如果 values 不是数组,就会抛出一个错误。错误消息包含了函数的名称,以及为什么会发生错误的明确描述。如果一个复杂的 Web 应用程序发生了这个错误,那么查找问题的根源也就容易多了。
【本文内容摘自:《JavaScript 高级程序设计》(第 3 版)Nicholas C.Zakas 著 李松峰 曹力 译】