Skip to content

JS 错误处理主要应用在大型项目,目前学习用的不多,但也要学习。

JS 中的错误分为语法错误和逻辑错误。

语法错误是很低级的错误,例如少写了一个括号,分号写成了中文,这种错误在预编译阶段就会发现,所谓的预编译,就是在执行之前,JS 会先给你检查一遍代码,顺便对你的代码做一些操作,比如前面提到的变量提升就是在预编译阶段完成的。

预编译发生在代码执行前,所以如果预编译阶段就发现了错误,那么整个脚本块一行代码也不会执行。

另一种是逻辑错误,或者说运行时错误,这里其实又可以分为两种,一种是运行报错,另一种是运行结果不符合预期。也就是说,对于这种错误而言,程序是会运行的,但是程序运行的结果和我们预期的结果不一样,或者干脆是在运行的过程中出现的错误。一个显而易见的运行时错误就是使用了未定义的变量

js
console.log(abc);

如上面的例子,由于没有定义变量 abc,这里直接使用,就直接报错,但这个报错是发生在代码运行过程中的。

js
console.log("before");//正常输出
console.log(abc);//报错
console.log("after");//不执行

运行上面的代码,会发现,当运行到 console.log(abc) 的时候,程序报错,此时后面的代码也不会执行,所以运行时错误,如果出现了报错,那么会立刻终止当前的脚本块,后续代码也不会执行。

而对于不符合预期的结果,这种也可以叫做错误,这种错误读者在编写代码的时候多多少少都遇到过,这里也不再展开讲解。

对于预编译阶段出现的语法错误,是非常显而易见的,所以我们也不详细介绍。这里主要讨论的都是运行时错误。要处理运行时错误有很多种方式。实际上,运行时错误一般都有两种情况,第一种情况是,这种错误不应该发生,另一种情况是,这种情况有可能发生。

如果我认为这个程序不应该有错,但是最后出现了错误,这个时候就要调试,如果是运行时直接报错了,那么控制台还会提示你报错的位置,可以比较快速地定位到错误位置,最头疼的是运行的结果和我们预期不同,这种情况我们只能考虑去一步一步调试,我们可以使用 console.log() 语句,在代码中添加打印语句,在控制台打印程序的各个环节变量的状态或者是一些时间点的关键信息,从而找到错误。这种方式非常方便,但是比较 low,也不够强大。这里推荐浏览器中的断点调试。

在浏览器控制台,一般会有一个源代码(source)界面,如下图所示:

![Pasted image 20240407232438](/notes-assets/02.JavaScript/5.标准库/assets/Pasted image 20240407232438.png) ![Pasted image 20240407232520](/notes-assets/02.JavaScript/5.标准库/assets/Pasted image 20240407232520.png) ![Pasted image 20240407232548](/notes-assets/02.JavaScript/5.标准库/assets/Pasted image 20240407232548.png)

以上是各个浏览器的调试控制工具的位置

![Pasted image 20240407233006](/notes-assets/02.JavaScript/5.标准库/assets/Pasted image 20240407233006.png)

我们可以通过点击某一行代码前面的位置,给这个代码添加一个端点,此时代码运行到这个位置就会暂停,此时再刷新页面,就会运行到这个位置暂停页面。

![Pasted image 20240407233329](/notes-assets/02.JavaScript/5.标准库/assets/Pasted image 20240407233329.png)

此时我们就可以通过这几个按钮进行调试,一般会有以下几个功能

  • 跨越(f 10)
  • 步进(f 11)
  • 步出(shift+f 11)

跨越实际上就是执行完当前语句,如果当前语句是函数,那么会直接执行该函数。步进也是执行当前语句,但如果是函数调用,那么会进入到函数内部进行执行。如果在函数内部的时候要立刻执行完当前函数。

如果是单纯就要一步一步调试,一些浏览器可能会提供步骤的功能,快捷键一般是 f9,这个可以帮助我们一步一步调试,即不管是不是函数调用都是一步一步进行调试。

可以使用 VSCODE 的 JavaScript Debugger 插件帮助我们在 VSCODE 中进行调试。

以上是第一种情况,也就是我们预期之外的错误,这个程序不应该出错,但是出错了,这个时候我们就去浏览器工具调试即可。除此之外还有一种情况,就是这个错误是我预期中的,也就是说我知道这里有可能会报错,比如一个函数用于判断一个数字是否是素数,但是这个时候传入了一个对象进来,让我判断这个对象是不是素数,这个时候就很可能报错,这个时候我们就要抛出这个错误。

在 JS 中,错误也是一个对象,我们抛出错误的语法为

js
throw 错误对象;//这里写什么都可以,只不过我们按道理应该写一个错误对象

错误对象是由 Error 函数创造的对象。

js
function test(n){
	if(Number.isNaN(n)){
		throw new Error("n必须是正常的数字");//参数里面填入的是错误消息
	}
}
test(NaN);
console.log(123);

实际上,使用 throw 错误对象,就可以实现手动报错的过程。

![Pasted image 20240408001028](/notes-assets/02.JavaScript/5.标准库/assets/Pasted image 20240408001028.png)

可以看到,这和系统的报错提示是一样的,只不过错误信息是我们自己写的,这里注意一个细节,这里出现了两个 at,即 at test (test.js:3:15) 和 at test.js:6:1,这其实就是错误调用栈,用于表示错误发生的位置,第一个 at test (test.js:3:15) 表示错误发生在 test 函数内,具体位置为 test.js 文件第三行的 15 个字符,由于这个函数内部并未处理这个错误,所以这个错误会向上抛出,就有了第二行 at test.js:6:1,表示这个错误位于 test.js 文件的第六行的第一个字符,也就是调用 test 函数的地方。此时错误被抛到了全局,还是没有处理,所以就会直接抛出到控制台,也就变成了我们看到的错误。

如果抛出的错误不被处理,那么会直接终止这个脚本块。

在 Error 对象中,其实也有一些属性可以让我们访问

  • Error.prototype.message:错误信息
  • Error.prototype.stack:错误调用栈的信息

实际上,Error 只是一个通用类型的错误,在 JS 中有很多类型的错误,每一个错误类型都有一个对应的构造函数,用于创造其对应的错误对象,JS 具体错误类型参见:Error - JavaScript | MDN。我们可以根据不同的情况选择不同的错误类型,这些错误没有什么本质性的区别,基本也只有下面的显示区域存在区别,根据合适的情况选择合适的错误类型,可以开发调试人员发现错误:
![Pasted image 20240408002647](/notes-assets/02.JavaScript/5.标准库/assets/Pasted image 20240408002647.png)

如果程序抛出了错误,程序会立刻停止执行,但是有时候这些错误并不致命,或者这个错误并不影响后续代码的执行,如果要让程序在抛出错误的情况下进行一些处理后继续执行代码,就要捕获这个错误。

捕获错误的语法为

js
try{
	//程序代码
}catch(e){//参数e为错误对象
	//错误处理代码
}finally{
	
}

try 后面的大括号写的就是可能要发生错误的程序代码,如果发生了错误,立刻执行 try 后面大括号中的代码,转而执行 catch 后面大括号中的代码,catch 可以添加一个小括号,相当于传入一个参数,这个参数为错误对象,我们可以在 catch 后面的大括号中拿到这个错误对象。而有时候不管发不发生错误,我在最后都需要执行一些代码,这些代码就放在 finally 后面的代码块中,也就是说 finally 中的代码是一定会执行的,不论如何都会执行,哪怕函数 return 掉了,最后也会执行 finally,如果在 finally 中执行了 return,由于 finally 是后执行,所以返回值一定是 finally 中 return 的结果,而不是 try 或者 catch 中的。

这个代码可以省略 finally 和 catch,但是一般不会省略 catch,因为省略 catch 之后这个 try 发生错误之后就不会被捕获到,相当于还是什么都不处理。

如果一个错误被捕获了,也就是像上面的方式进入了 catch 函数处理了之后,这个错误就相当于被处理完了,后面的代码也会继续执行。

以笔记为镜,可以知得失