文章目录
  1. 1. Error对象
  2. 2. JavaScript的原生错误类型
    1. 2.1. SyntaxError
    2. 2.2. ReferenceError
    3. 2.3. RangeError
    4. 2.4. TypeError
    5. 2.5. URIError
    6. 2.6. EvalError
  3. 3. 自定义错误
  4. 4. throw语句
  5. 5. try…catch结构
  6. 6. finally代码块

Error对象

JavaScript 原生提供Error对象,所有抛出的错误都是这个对象的实例。

1
var err = new Error('出错了');

当代码解析或运行时发生错误,JavaScript引擎就会自动产生并抛出一个Error对象的实例,然后整个程序就中断在发生错误的地方。

根据语言标准,Error对象的实例必须有message属性,表示出错时的提示信息,其他属性则没有提及。大多数JavaScript引擎,对Error实例还提供namestack属性,分别表示错误的名称和错误的堆栈,但它们是非标准的,不是每种实现都有。

  1. message:错误提示信息
  2. name:错误名称(非标准属性)
  3. stack:错误的堆栈(非标准属性)
    利用namemessage这两个属性,可以对发生什么错误有一个大概的了解。
    1
    2
    3
    if (error.name){
    console.log(error.name + ": " + error.message);
    }

上面代码表示,显示错误的名称以及出错提示信息。

stack属性用来查看错误发生时的堆栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function throwit() {
throw new Error('');
}

function catchit() {
try {
throwit();
} catch(e) {
console.log(e.stack); // print stack trace
}
}

catchit()
// Error
// at throwit (~/examples/throwcatch.js:9:11)
// at catchit (~/examples/throwcatch.js:3:9)
// at repl:1:5

上面代码显示,抛出错误首先是在throwit函数,然后是在catchit函数,最后是在函数的运行环境中。

JavaScript的原生错误类型

Error对象是最一般的错误类型,在它的基础上,JavaScript还定义了其他6种错误,也就是说,存在Error的6个派生对象。

SyntaxError

SyntaxError是解析代码时发生的语法错误。

1
2
3
4
5
// 变量名错误
var 1a;

// 缺少括号
console.log 'hello');

ReferenceError

ReferenceError是引用一个不存在的变量时发生的错误。

1
2
unknownVariable
// ReferenceError: unknownVariable is not defined

另一种触发场景是,将一个值分配给无法分配的对象,比如对函数的运行结果或者this赋值。

1
2
3
4
5
console.log() = 1
// ReferenceError: Invalid left-hand side in assignment

this = 1
// ReferenceError: Invalid left-hand side in assignment

上面代码对函数console.log的运行结果和this赋值,结果都引发了ReferenceError错误。

RangeError

RangeError是当一个值超出有效范围时发生的错误。主要有几种情况,一是数组长度为负数,二是Number对象的方法参数超出范围,以及函数堆栈超过最大值。

1
2
3
4
5
new Array(-1)
// RangeError: Invalid array length

(1234).toExponential(21)
// RangeError: toExponential() argument must be between 0 and 20

TypeError

TypeError是变量或参数不是预期类型时发生的错误。比如,对字符串、布尔值、数值等原始类型的值使用new命令,就会抛出这种错误,因为new命令的参数应该是一个构造函数。

1
2
3
4
5
6
new 123
//TypeError: number is not a func

var obj = {};
obj.unknownMethod()
// TypeError: undefined is not a function

上面代码的第二种情况,调用对象不存在的方法,会抛出TypeError错误。

URIError

URIError是URI相关函数的参数不正确时抛出的错误,主要涉及encodeURI()decodeURI()encodeURIComponent()decodeURIComponent()escape()unescape()这六个函数。

1
2
decodeURI('%2')
// URIError: URI malformed

EvalError

eval函数没有被正确执行时,会抛出EvalError误。该错误类型已经不再在ES5中出现了,只是为了保证与以前代码兼容,才继续保留。

以上这6种派生错误,连同原始的Error对象,都是构造函数。开发者可以使用它们,人为生成错误对象的实例。

1
2
3
new Error('出错了!');
new RangeError('出错了,变量超出有效范围!');
new TypeError('出错了,变量类型无效!');

上面代码新建错误对象的实例,实质就是手动抛出错误。可以看到,错误对象的构造函数接受一个参数,代表错误提示信息(message)。

自定义错误

除了JavaScript内建的7种错误对象,还可以定义自己的错误对象。

1
2
3
4
5
6
7
function UserError(message) {
this.message = message || "默认信息";
this.name = "UserError";
}

UserError.prototype = new Error();
UserError.prototype.constructor = UserError;

上面代码自定义一个错误对象UserError,让它继承Error对象。然后,就可以生成这种自定义的错误了。

1
new UserError("这是自定义的错误!");

throw语句

throw语句的作用是中断程序执行,抛出一个意外或错误。它接受一个表达式作为参数,可以抛出各种值。

1
2
3
4
5
6
7
8
9
10
11
// 抛出一个字符串
throw "Error!";

// 抛出一个数值
throw 42;

// 抛出一个布尔值
throw true;

// 抛出一个对象
throw {toString: function() { return "Error!"; } };

上面代码表示,throw可以接受各种值作为参数。JavaScript引擎一旦遇到throw语句,就会停止执行后面的语句,并将throw语句的参数值,返回给用户。

如果只是简单的错误,返回一条出错信息就可以了,但是如果遇到复杂的情况,就需要在出错以后进一步处理。这时最好的做法是使用throw语句手动抛出一个Error对象。

1
throw new Error('出错了!');

上面语句新建一个Error对象,然后将这个对象抛出,整个程序就会中断在这个地方。

throw语句还可以抛出用户自定义的错误。

1
2
3
4
5
6
7
8
9
10
function UserError(message) {
this.message = message || "默认信息";
this.name = "UserError";
}

UserError.prototype.toString = function (){
return this.name + ': "' + this.message + '"';
}

throw new UserError("出错了!");

可以通过自定义一个assert函数,规范化throw抛出的信息。

1
2
3
4
function assert(expression, message) {
if (!expression)
throw {name: 'Assertion Exception', message: message};
}

上面代码定义了一个assert函数,它接受一个表达式和一个字符串作为参数。一旦表达式不为真,就抛出指定的字符串。它的用法如下。

1
assert(typeof myVar != 'undefined', 'myVar is undefined!');

console对象的assert方法,与上面函数的工作机制一模一样,所以可以直接使用。

1
console.assert(typeof myVar != 'undefined', 'myVar is undefined!');

try…catch结构

为了对错误进行处理,需要使用try...catch结构。

1
2
3
4
5
6
7
8
9
try {
throw new Error('出错了!');
} catch (e) {
console.log(e.name + ": " + e.message);
console.log(e.stack);
}
// Error: 出错了!
// at <anonymous>:3:9
// ...

上面代码中,try代码块一抛出错误(上例用的是throw语句),JavaScript引擎就立即把代码的执行,转到catch代码块。可以看作,错误可以被catch代码块捕获。catch接受一个参数,表示try代码块抛出的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function throwIt(exception) {
try {
throw exception;
} catch (e) {
console.log('Caught: '+ e);
}
}

throwIt(3);
// Caught: 3
throwIt('hello');
// Caught: hello
throwIt(new Error('An error happened'));
// Caught: Error: An error happened

上面代码中,throw语句先后抛出数值、字符串和错误对象。

catch代码块捕获错误之后,程序不会中断,会按照正常流程继续执行下去。

1
2
3
4
5
6
7
8
try {
throw "出错了";
} catch (e) {
console.log(111);
}
console.log(222);
// 111
// 222

上面代码中,try代码块抛出的错误,被catch代码块捕获后,程序会继续向下执行。

catch代码块之中,还可以再抛出错误,甚至使用嵌套的try...catch结构。

1
2
3
4
5
6
7
8
9
10
11
var n = 100;

try {
throw n;
} catch (e) {
if (e <= 50) {
// ...
} else {
throw e;
}
}

上面代码中,catch代码之中又抛出了一个错误。

为了捕捉不同类型的错误,catch代码块之中可以加入判断语句。

1
2
3
4
5
6
7
8
9
10
try {
foo.bar();
} catch (e) {
if (e instanceof EvalError) {
console.log(e.name + ": " + e.message);
} else if (e instanceof RangeError) {
console.log(e.name + ": " + e.message);
}
// ...
}

上面代码中,catch捕获错误之后,会判断错误类型(EvalError还是RangeError),进行不同的处理。

try...catch结构是JavaScript语言受到Java语言影响的一个明显的例子。这种结构多多少少是对结构化编程原则一种破坏,处理不当就会变成类似goto语句的效果,应该谨慎使用。

finally代码块

try...catch结构允许在最后添加一个finally代码块,表示不管是否出现错误,都必需在最后运行的语句。

1
2
3
4
5
6
7
8
9
10
11
12
function cleansUp() {
try {
throw new Error('出错了……');
console.log('此行不会执行');
} finally {
console.log('完成清理工作');
}
}

cleansUp()
// 完成清理工作
// Error: 出错了……

上面代码中,由于没有catch语句块,所以错误没有捕获。执行finally代码块以后,程序就中断在错误抛出的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
function idle(x) {
try {
console.log(x);
return 'result';
} finally {
console.log("FINALLY");
}
}

idle('hello')
// hello
// FINALLY
// "result"

上面代码说明,即使有return语句在前,finally代码块依然会得到执行,且在其执行完毕后,才会显示return语句的值。

下面的例子说明,return语句的执行是排在finally代码之前,只是等finally代码执行完毕后才返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
var count = 0;
function countUp() {
try {
return count;
} finally {
count++;
}
}

countUp()
// 0
count
// 1

上面代码说明,return语句的count的值,是在finally代码块运行之前,就获取完成了。

下面是finally代码块用法的典型场景。

1
2
3
4
5
6
7
8
9
openFile();

try {
writeFile(Data);
} catch(e) {
handleError(e);
} finally {
closeFile();
}

上面代码首先打开一个文件,然后在try代码块中写入文件,如果没有发生错误,则运行finally代码块关闭文件;一旦发生错误,则先使用catch代码块处理错误,再使用finally代码块关闭文件。

下面的例子充分反应了try...catch...finally这三者之间的执行顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function f() {
try {
console.log(0);
throw "bug";
} catch(e) {
console.log(1);
return true; // 这句原本会延迟到finally代码块结束再执行
console.log(2); // 不会运行
} finally {
console.log(3);
return false; // 这句会覆盖掉catch中的那句return
console.log(4); // 不会运行
}

console.log(5); // 不会运行
}

var result = f();
// 0
// 1
// 3

result
// false

上面代码中,catch代码块结束执行之前,会先执行finally代码块。从catch转入finally的标志,不仅有return语句,还有throw语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function f() {
try {
throw '出错了!';
} catch(e) {
console.log('捕捉到内部错误');
throw e; // 这句原本会等到finally结束再执行,但是由于finally中有return,直接return了,就没有执行到
} finally {
return false; // 直接返回
}
}

try {
f();
} catch(e) {
// 此处不会执行
console.log('caught outer "bogus"');
}

// 捕捉到内部错误

上面代码中,进入catch代码块之后,一遇到throw语句,就会去执行finally代码块,其中有return false语句,因此就直接返回了,不再会回去执行catch代码块剩下的部分了。

转自:阮一峰教程

文章目录
  1. 1. Error对象
  2. 2. JavaScript的原生错误类型
    1. 2.1. SyntaxError
    2. 2.2. ReferenceError
    3. 2.3. RangeError
    4. 2.4. TypeError
    5. 2.5. URIError
    6. 2.6. EvalError
  3. 3. 自定义错误
  4. 4. throw语句
  5. 5. try…catch结构
  6. 6. finally代码块