# JavaScript教程 - 18 异常处理
# 18.1 异常
JavaScript 已经是比较”宽容“的语言了,有一些情况,搁其他语言早就报错了,但是 JavaScript 还能运行,因为 JavaScript 在设计的时候,设计思想就是能不报错就不报错。
但是也不是所有情况 JavaScript 都不报错,有些时候程序确实是无法继续执行的,不得不报错并终止执行,我们可以对这些可能出现的错误情况进行预处理,防止程序崩溃或者给用户一些提示,这样可以增强代码的健壮性和提升用户体验。
举个栗子,有下面的代码,执行的时候会报错:
let obj = null;
console.log(obj.name);
2
上面的代码会报错,错误会在浏览器控制台打印:
# 18.2 异常处理
异常处理的语法结构:
try {
// 可能出错的代码
} catch (error) {
// 捕获异常后的处理逻辑
} finally {
// (可选)无论是否出错都会执行
}
2
3
4
5
6
7
- 将可能报错的代码放到 try 块中;
- 如果代码运行出现异常,会被 catch 块捕获,并执行 catch 块中的代码;
- 无论代码是否出错,finally 块的代码都会执行,finally 块可以省略。
举个栗子:
try {
// 可能出错的代码
let obj = null;
console.log(obj.name); // TypeError
console.log(123); // 无法执行
} catch (error) {
console.log("出错了:" + error);
} finally {
console.log("总会执行的代码,如清理资源");
}
console.log(456); // 可以执行
2
3
4
5
6
7
8
9
10
11
12
13
- 当 try 块中的代码出现异常,那么 try 块中出现异常的代码之后的代码,是无法执行的,所以上面是无法打印
123
的。 - 出现异常,会被 catch 块捕获,异常信息会被封装到 error (名称可自定义)对象中。
- 无论是否出现异常,finally 块的代码都会执行,如果需要,可以添加 finally 块,不需要可以省略;
- 因为异常被捕获了,所以之后的代码是可以继续执行的,所以会打印
456
。 - 注意:非常不建议 catch 块中什么代码也不写,这样出现异常,程序没有成功执行,还没有任何日志,不容易定位错误。所以最起码打印一下日志。
上面的代码执行结果如下:
出错了:Cannot read properties of null (reading 'name')
总会执行的代码,如清理资源
456
2
3
# 18.3 打印错误信息
错误信息会被封装到异常对象中,可以通过如下信息打印异常信息:
try {
console.log(obj.name); // TypeError
} catch (error) {
console.log(error); // 1. 打印完整信息
console.error(error); // 2. 打印完整信息,红色更显眼
console.log("出错了:" + error) // 3. 打印简短的信息,包括类型和错误信息
console.log(e.message); // 4. 只输出错误信息
console.log(e.name); // 5. 打印错误类型
console.log(e.stack); // 6. 打印调用栈信息
}
// ----打印信息如下:
// 1
ReferenceError: obj is not defined
at index.html:10:21
// 2
index.html:13 ReferenceError: obj is not defined
at index.html:10:21
(匿名) @ index.html:13
// 3
出错了:ReferenceError: obj is not defined
// 4
出错了:obj is not defined
// 1
ReferenceError: obj is not defined
at index.html:10:21
// 2
index.html:13 ReferenceError: obj is not defined
at index.html:10:21
(匿名) @ index.html:13
// 3
出错了:ReferenceError: obj is not defined
// 4
obj is not defined
// 5
ReferenceError
// 6
ReferenceError: obj is not defined
at index.html:10:21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
- 可以根据需要打印,开发时可以打印详细一些,将堆栈信息打印出来,例如查找错误。
# 18.4 异常的传递
什么是异常的传递?
异常的传递,就是当方法执行的时候出现异常,如果没有进行捕获,就会将异常传递给该方法的调用者,如果调用者仍未处理异常,则继续向上传递,直到传递给主程序,如果主程序仍然没有进行异常处理,则程序将被终止。
举个栗子:
function nullError() {
let obj = null;
console.log(obj.name); // 会出错的代码
}
function testError() {
nullError(); // 调用会抛出异常
}
try {
testError(); // 这里会抛出异常
} catch (error) {
console.log(error);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 上面代码,执行
nullError()
会抛出空指针异常,因为nullError()
函数中没有进行异常处理,则异常会抛给testError()
函数,testError()
没有进行异常处理,会继续向上抛给调用者,如果有调用者对异常进行了处理,程序不会崩溃。
利用异常的传递性,如果我们在程序运行的开始位置进行了异常捕获,无论程序哪里发生了错误,最终都会被传递到一开始调用的位置,就像上面那样,这样可以保证所有的异常都会被捕获。但是不要所有的异常都在这里处理,这只是兜底处理,至于异常在哪里地方处理,还是根据情况来确定。
# 18.5 主动抛出异常
有时候我们可以根据需要,主动抛出异常。
举个栗子:
function divide(a, b) {
if (b === 0) {
throw new Error("除数不能为 0"); // 主动抛出异常
}
return a / b;
}
try {
let result = divide(5, 0);
console.log("结果是:" + result);
} catch (e) {
console.log("捕获异常:" + e.message);
alert('计算出错,请检查数据是否正确');
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 上面的代码中,
divide
函数在除数为 0 的时候,主动抛出异常; - 调用的地方,可以进行异常捕获,并对异常进行处理。例如可以提示用户等处理。
# 18.6 异步函数的异常
需要注意:try...catch
只能捕获同步代码中的异常。
看一下下面的代码:
function test() {
throw new Error(); // 抛出异常
}
try {
setTimeout(test, 1000);
console.log("done");
} catch (e) {
console.log("error:" + e.message);
}
2
3
4
5
6
7
8
9
10
- 上面的
try...catch
并不能捕获test()
函数的异常,test()
函数并不是立即被执行,当test()
函数被执行的时候,主程序已经执行完成了。
如果要捕获只能在回调函数中进行捕获:
function test() {
try {
throw new Error();
} catch (e) {
console.log('error:', e.message);
}
}
2
3
4
5
6
7
# 18.7 自定义异常
JavaScript 内置了一些异常类型,常见的如下:
异常类型 | 继承自 | 说明 | 示例 |
---|---|---|---|
Error | 无 | 所有错误的基类 | throw new Error("通用错误") |
TypeError | Error | 类型错误,如方法调用、访问属性时类型不匹配 | null.name 或 123.toUpperCase() |
ReferenceError | Error | 引用不存在的变量 | console.log(a) (未定义) |
SyntaxError | Error | 代码语法错误,如 eval('foo bar') | JSON.parse("{name: 'Tom'}") |
RangeError | Error | 数值超出范围,如递归太深、数组长度非法 | new Array(-1) |
URIError | Error | URI 格式错误,如 decodeURIComponent 无效 | decodeURIComponent('%') |
EvalError | Error | eval() 使用错误(很少见) | 特殊场景 |
有时候程序有问题,可能会自动抛出上面的一些异常,我们也可以手动创建这些异常对象,并抛出:
throw new Error("除数不能为 0");
// 也可以创建其他的异常对象
throw new URIError("URI格式错误,请检查");
2
3
4
在捕获的时候,也可以通过错误对象,判断是什么类型的异常:
try {
// ...
} catch (error) {
console.log("出错了:" + error.message);
// 判断异常类型
if (error instanceof TypeError) {
console.log("类型错误!");
} else if (error instanceof ReferenceError) {
console.log("引用错误!");
} else {
console.log("其他错误");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
在进行开发的时候,我们可以针对不同的情况抛出不同的异常,然后针对不同的异常进行不同的处理,这种情况下,内置的异常类型局限性太大,我们可以自定义异常类型。
一般情况下,我们可以通过继承 Error
创建自己的异常类型。
举个栗子:
// 定义用户名验证异常类
class UsernameValidationError extends Error {
constructor(message) {
super(message);
this.name = "UsernameValidationError";
}
}
// 定义密码验证异常类
class PasswordValidationError extends Error {
constructor(message) {
super(message);
this.name = "PasswordValidationError";
}
}
//-----------------------------------
// 校验用户名
function validateUsername(username) {
if (typeof username !== "string" || username.trim().length < 3) {
throw new UsernameValidationError("用户名必须是至少3个字符的字符串"); // 用户名格式不正确时,抛出异常
}
}
// 校验密码
function validatePassword(password) {
if (typeof password !== "string" || password.length < 6) {
throw new PasswordValidationError("密码必须至少6位"); // 密码格式不正确时,抛出异常
}
}
//-----------------------------------
// 使用示例
try {
const username = "foooor.com"; // 太短
const password = "123"; // 太短
validateUsername(username);
validatePassword(password);
console.log("校验通过,允许登录");
} catch (err) {
// 根据类型分别处理
if (err instanceof UsernameValidationError) {
console.log("用户名格式错误:" + err.message);
alert('用户名格式不正确');
} else if (err instanceof PasswordValidationError) {
console.log("密码格式错误:" + err.message);
alert('密码格式不正确');
} else {
console.log("未知错误:" + err.message);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
- 在上面的代码中,首先定义了两个异常类型
UsernameValidationError
和PasswordValidationError
继承 Error 类。 - 然后编写了两个校验方法,如果用户名和密码不正确就抛出响应的异常。
- 在使用的时候,直接调用校验方法,并进行异常捕获,在捕获到异常的时候,进行异常类型判断,判断是用户名还是密码格式不正确,并给出提示。
← 17-BOM