在ES3之前js代码执行的过程中,一旦出现错误,整个js代码都会停止执行,这样就显的代码非常的不健壮。
在Java或C#等一些高级语言中,都提供了异常处理机制,可以处理出现的异常,而不会停止整个应用程序。
从ES3开始,js也提供了类似的异常处理机制,从而让js代码变的更健壮,即使执行的过程中出现了异常,也可以让程序具有了一定的异常恢复能力。
Error对象
JavaScript 解析或运行时,一旦发生错误,引擎就会抛出一个错误对象。JavaScript 原生提供Error构造函数,所有抛出的错误都是这个构造函数的实例。
const err = new Error('我是好人');
console.log(err.message) // "我是好人"
上面代码中,我们调用Error() 构造函数,生成一个实例对象err。Error() 构造函数接受一个参数,表示错误提示,可以从实例的message属性读到这个参数。
JavaScript 语言标准只提到,Error 实例对象必须有message属性,表示出错时的提示信息,没有提到其他属性。大多数 JavaScript 引擎,对Error实例还提供name和stack属性,分别表示错误的名称和错误的堆栈,但它们是非标准的,不是每种实现都有。
- message:错误提示信息(标准属性)
- name:错误名称(非标准属性)
- stack:错误的堆栈(非标准属性)
使用name和message这两个属性,可以对发生什么错误有一个大概的了解。
if (error.name) {
console.log(error.name + ': ' + error.message);
}
stack属性用来查看错误发生时的堆栈。
function throwError() {
throw new Error('小错要抛出来啦');
}
function catchError() {
try {
throwError();
} catch(e) {
console.log(e.stack); // print stack trace
}
}
catchError()
// Error: 小错要抛出来啦
// at throwError (<anonymous>:2:9)
// at catchError (<anonymous>:7:5)
// at <anonymous>:13:1
上面代码中,错误堆栈的最内层是throwError函数,然后是catchError函数,最后是函数的运行环境。
js内置错误类型
Error实例对象是最一般的错误类型,在它的基础上,JavaScript 还定义了6个派生对象。
SyntaxError 对象
SyntaxError对象是解析代码时发生的语法错误。
// 变量名错误
const 1aa;
// Uncaught SyntaxError: Invalid or unexpected token
// 缺少括号
console.log 'hi');
// Uncaught SyntaxError: Unexpected string
上面代码的错误,都是在语法解析阶段就可以发现,所以会抛出SyntaxError。第一个错误提示是“token 非法”,第二个错误提示是“字符串不符合要求”。
ReferenceError 对象
ReferenceError对象是引用一个不存在的变量时发生的错误。
// 使用一个不存在的变量
unknownVariable
// Uncaught ReferenceError: unknownVariable is not defined
另一种触发场景是,将一个值分配给无法分配的对象,比如对函数的运行结果赋值。
// 等号左侧不是变量
console.log() = 1
// Uncaught ReferenceError: Invalid left-hand side in assignment
上面代码对函数console.log的运行结果赋值,结果引发了ReferenceError错误。
TypeError 对象
TypeError对象是变量或参数不是预期类型时发生的错误。比如,对字符串、布尔值、数值等基本数据类型的值使用new命令,就会抛出这种错误,因为new命令的参数应该是一个构造函数。
new 123
// Uncaught TypeError: 123 is not a constructor
const obj = {};
obj.unknownMethod()
// Uncaught TypeError: obj.unknownMethod is not a function
上面代码的第二种情况,调用对象不存在的方法,也会抛出TypeError错误,因为obj.unknownMethod的值是undefined,而不是一个函数。
RangeError 对象
RangeError对象是一个值超出有效范围时发生的错误。主要有几种情况,一是数组长度为负数,二是Number对象的方法参数超出范围,以及函数堆栈超过最大值。
// 数组长度不得为负数
new Array(-1)
// Uncaught RangeError: Invalid array length
// Number对象的方法参数超出范围
const num = new Number(12.34)
num.toFixed(-1)
// Uncaught RangeError: toFixed() digits argument must be between 0 and 100
URIError 对象
URIError对象是 URI 相关函数的参数不正确时抛出的错误,主要涉及encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape() 和unescape() 这六个函数。
decodeURI('%2')
// Uncaught URIError: URI malformed
注: escape()和unescape() 已废弃
EvalError 对象
eval函数没有被正确执行时,会抛出EvalError错误。该错误类型已经不再使用了,只是为了保证与以前代码兼容,才继续保留。
总结
以上这6种派生错误,连同原始的Error对象,都是构造函数。开发者可以使用它们,手动生成错误对象的实例。这些构造函数都接受一个参数,代表错误提示信息(message)。
const err1 = new Error('出错了!');
const err2 = new RangeError('出错了,变量超出有效范围!');
const err3 = new TypeError('出错了,变量类型无效!');
err1.message // "出错了!"
err2.message // "出错了,变量超出有效范围!"
err3.message // "出错了,变量类型无效!"
一般情况,不同的错误,处理方式不一样。可以参考下面的处理方式。不过在实际开发中,大多数开发者并没有对错误类型进行进一步的处理。
try {
someFunction();
} catch (error){
if (error instanceof TypeError){
//处理类型错误
} else if (error instanceof ReferenceError){
//处理引用错误
} else {
//处理其他的错误
}
}
new CustomError('哦,报错了')
throw 异常
抛出js内置错误类型的对象
在大部分的代码执行过程中,都是出现错误的时候,由浏览器(javascript引擎)抛出异常,然后程序或停止执行,或被try…catch 捕获。
然而有时候我们在检测到一些不合理的情况发生的时候也可以主动抛出错误。
使用 throw 关键字抛出来主动抛出异常。
throw new Error("看,报错了吧");
console.log("执行不到这里的")
注意:
- thow后面就是我们要抛出的异常对象。在以前的时候都是出现错误的时候浏览器抛出异常对象,只是现在是我们自己主动抛出的异常对象。
- 只要有异常对象抛出,不管是浏览器抛出的,还是代码主动抛出,都会让程序停止执行。如果想让程序继续执行,则有也可以用try…catch来捕获。
- 每一个错误类型都可以传入一个参数,表示实际的错误信息。
- 我们可以在适当的时候抛出任何我们想抛出的异常类型。throw new SyntaxError(“语法错误…”);
看下面的代码:
/*该函数接收一个数字,返回他的平方。*/
function foo(num) {
if(typeof num == "number"){
return num * num;
}else{
throw new TypeError("类型错误,你应该传入一个数字...")
}
}
console.log(foo(33))
console.log(foo("abc"))
throw可以抛出任何类型的值。也就是说,它的参数可以是任何值。
// 抛出一个字符串
throw 'Error!';
conole.log('看看能不能执行到这里')
// Uncaught Error!
// 抛出一个数值
throw 42;
conole.log('看看能不能执行到这里')
// Uncaught 42
// 抛出一个布尔值
throw true;
conole.log('看看能不能执行到这里')
// Uncaught true
// 抛出一个对象
throw {
toString: function () {
return 'Error!';
}
};
conole.log('看看能不能执行到这里')
// Uncaught {toString: ƒ}
抛出自定义类型的错误对象
我们不仅仅可以抛出js内置的错误类型的对象,也可以自定义错误类型,然后抛出自定义错误类型的对象。
如果要自定义错误类型,只需要继承任何一个js错误类型都可以。一般直接继承Error即可。
function CustomError(message) {
this.message = message ?? '默认错误消息提示'
this.name = 'CustomError'
}
CustomError.prototype = new Error(); // 这句不写可以试试输出看
try {
throw new CustomError("注意:这是自定义错误类型")
}catch (error){
console.log(error.message)
}
上面代码自定义一个错误对象CustomError,让它继承Error对象。然后,就可以生成这种自定义类型的错误了。
异常捕获
基本的try…catch语句
ES3开始引入了 try-catch 语句,是 JavaScript 中处理异常的标准方式。
语法:
try{
//可能发生异常的代码
}catch(error){
//发生错误执行的代码
}
看下面的代码:
try{
console.log(b);
console.log("我不会输出的,不要找了")
}catch(error){
console.log("发生错误了")
}
console.log("我try catch后面的代码")
说明:
- 把有可能出的问题的代码放在 try 语句中。try语句中可以理论上可以写任何的代码,只要有一行代码出现问题,整个程序的执行流程就会立即调到catch语句中执行。
- 一旦try中有一行代码发生异常,则这行出错代码的后面的try中的其他语句都不会再执行。比如上面代码中的console.log(b);这行代码会出错,则立即去执行catch中的代码。所以console.log(“我不会输出的,不要找了”)这行代码则不会再执行
- 在执行catch中的代码之前,js引擎会首先根据错误类型自动创建一个错误,并通过catch后面的参数传递到catch中。不同的浏览器创建的error对象不一样,但是他们都包含一个message属性,值是这个错误的一些信息。
- catch中的代码执行完毕之后,会继续执行后面的代码,程序不会停止下来。
finally语句
在 try…catch 中,try 中一旦出现错误则紧接着的js语句不会执行,如果不出现错误则 catch 中的语句不会执行。
Javascript 参考其他编程语言,也提供了一种 finally 语句:不管 try 中的语句有没有错误,在最后都会执行 finally 中的语句。
即:try 中语句不发生错误执行完毕后会执行 finally 中的语句,try 中的语句发生错误,则执行 catch中的语句,catch 中的语句执行完毕后也会执行 finally 中的语句。
语法:
try{
}catch(error){
}finally{
}
try{
console.log(b);
console.log("我不会输出的,不要找了")
}catch(error){
console.log("发生错误了")
}finally{
console.log("不管发生不发生错误,我都会执行")
}
console.log("我try catch后面的代码")
所以在 finally 中我们可以放置我们必须要执行的代码。
注意:
- 在js中,如果添加了 finally 语句,则 catch 语句可以省略。所以下面的代码也是正确的。
- 如果没有 catch 语句,则一旦发生错误就无法捕获这个错误,所以在执行完 finally 中的语句后,程序就会立即停止了。
- 所以,在实际使用中,最好一直带着 catch 语句。
try{
console.log(b);
console.log("我不会输出的,不要找了")
}finally{
console.log("不管发生不发生错误,我都会执行")
}
console.log("我try catch后面的代码")
用例
function returnTest(x) {
try {
console.log(x);
return 'result';
} finally {
console.log('FINALLY');
}
}
const result = returnTest('hello')
console.log(result)
// hello
// FINALLY
// result
上面代码中,try代码块没有发生错误,而且里面还包括return语句,但是finally代码块依然会执行。而且,这个函数的返回值还是result。
下面的例子说明,return语句的执行是排在finally代码之前,只是等finally代码执行完毕后才返回。
let count = 0;
function countUp() {
try {
return count;
} finally {
count++;
}
}
console.log(countUp())
// 0
console.log(count)
// 1
上面代码说明,return语句里面的count的值,是在finally代码块运行之前就获取了。
下面的例子充分反映了try…catch…finally这三者之间的执行顺序。
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; // 这句会覆盖掉前面那句 return
console.log(4); // 不会运行
}
console.log(5); // 不会运行
}
var result = f();
// 0
// 1
// 3
console.log(result)
// false
上面代码中,catch代码块结束执行之前,会先执行finally代码块。
catch代码块之中,触发转入finally代码块的标志,不仅有return语句,还有throw语句。
function f() {
try {
throw '出错了!';
} catch(e) {
console.log('捕捉到内部错误');
throw e; // 这句原本会等到finally结束再执行
} finally {
return false; // 直接返回
}
}
try {
const result = f();
console.log(result)
} catch(e) {
// 此处不会执行
console.log('caught outer "bogus"');
}
// 捕捉到内部错误
// false
上面代码中,进入catch代码块之后,一遇到throw语句,就会去执行finally代码块,其中有return false语句,因此就直接返回了,不再会回去执行catch代码块剩下的部分了。
try代码块内部,还可以再使用try代码块。
try {
try {
consle.log('Hello world!'); // 报错
} finally {
console.log('Finally');
}
console.log('Will I run?');
} catch (error) {
console.error(error.message);
}
// Finally
// consle is not defined
上面代码中,try里面还有一个try。内层的try报错(console拼错了),这时会执行内层的finally代码块,然后抛出错误,被外层的catch捕获。
合理使用try…catch
当 try-catch 语句中发生错误时,浏览器会认为错误已经被处理了,浏览器就不再报告错误了。这也是最简单的一种情况。
使用 try-catch 最适合处理那些我们无法控制的错误。假设你在使用一个大型 JavaScript 库中的 函数,该函数可能会有意无意地抛出一些错误。由于我们不能修改这个库的源代码,所以大可将对该函数的调用放在 try-catch 语句当中,一有什么错误发生,也好可以恰当地处理它们。
在明明知道自己的代码会发生错误时,再使用 try-catch 语句就不太合适了。例如,如果传给函数的参数是字符串而非数值,就会造成函数出错,那么就应该先检查参数的类型,然后再决定如何去做。在这种情况下,不应用使用 try-catch 语句。因为try…catch语句是比较耗资源的事情。