错误是带有类型的对象
JavaScript 抛出错误时,交给你的不只是一个字符串,而是一个对象。这个对象有一个类型(它的构造函数),还有几个标准属性:name、message 和 stack。
err.name 是一个简短的标签,比如 "TypeError"。err.message 是人类可读的描述。err instanceof TypeError 告诉你具体是哪个类。了解错误类型很重要:它能告诉你问题出在拼写错误、错误的值,还是代码根本没能被解析。
内置的错误类型共有七种。其中三种你会经常见到,另外四种偶尔才会碰上。
SyntaxError:代码无法解析
SyntaxError 表示 JavaScript 连你的代码都读不懂。它其实不算运行时错误:引擎在解析阶段就失败了,一行代码都还没运行。你无法在同一个文件中用 try/catch 捕获 SyntaxError,因为整个文件都被拒绝了。
function greet(name {
return "hi, " + name;
}
// SyntaxError: Unexpected token '{'
少了括号、多了逗号、在函数外面写 return,任何违反语法的写法都会抛出这个错误。修复方法永远是改正源代码。唯一能捕获 SyntaxError 的场合,是在运行时进行解析,比如 JSON.parse:
JSON.parse 在运行时接收一个字符串,所以它的语法错误是可以捕获的。而你自己源文件中的语法错误则不行。
ReferenceError:这个名称不存在
当你引用一个在当前代码可见的任何作用域中都没有声明过的变量时,就会触发 ReferenceError。
九成的情况是拼写错误(把 total 写成了 totl)。剩下一成是作用域问题:你在试图使用另一个函数或模块中声明的东西。
还有一个更隐蔽的原因:暂时性死区。let 和 const 声明从所在块的顶部就已存在,但在声明它们的那一行之前,你无法访问它们:
运行 console.log(x) 时,x 已经是一个真实存在的绑定,只是还没有被初始化。因此出现了引用错误。修复方法是把访问语句移到声明之后。
TypeError:值的类型不对
TypeError 表示值是存在的,但它不是这个操作所期望的那种东西。调用一个不是函数的东西、读取 null 或 undefined 的属性、给 const 重新赋值,这些都会抛出 TypeError。
“Cannot read properties of null (reading 'name')”大概是 JavaScript 中最常见的错误信息。修复方法要么是确保这个值存在,要么是用可选链保护这次访问:user?.name。
TypeError 的其他形式:
调用一个数字、给 const 重新赋值、调用一个不存在的方法:值的类型不适合你要求它做的事情。
RangeError:数字超出范围
当一个数字在技术上合法,但超出了某个操作允许的范围时,就会触发 RangeError。
最典型的来源是无限递归,它会撑爆调用栈:
“Maximum call stack size exceeded”几乎总是意味着某个函数在没有终止条件的情况下调用自身,或者两个函数在循环中互相调用。
URIError 和 EvalError:少见的两种
当 URI 处理函数(encodeURI、decodeURIComponent 等)收到格式错误的输入时,就会抛出 URIError:
EvalError 是一个历史遗留物。现代 JavaScript 引擎实际上不会在任何情况下抛出它,它作为构造函数保留下来,只是为了向后兼容。你可以手动创建一个,但在实际代码中不会遇到它。
继承链
所有这些类型都继承自基类 Error。这意味着对它们中的任何一个,err instanceof Error 都为 true,在通用的 catch 中这很方便:
catch 块会捕获一切,包括有人写 throw "oops" 时抛出的非错误值。所以最后那个分支很重要。在把捕获到的值当作错误对象处理之前,一定要先用 instanceof 缩小范围。
主动创建错误
当你的代码检测到问题时,可以自己抛出任何一种内置类型的错误。选择与失败情况相匹配的类型:
让错误类型与失败情况相匹配,并不只是为了好看:它让调用方可以写出有针对性的 catch 逻辑,而不必去解析错误信息。
自定义错误类
当内置类型都不合适时,就继承 Error:
有两点要记住:调用 super(message),让基类 Error 正确初始化;设置 this.name,让日志显示正确的标签。像 field 这样的自定义字段,让调用方无需解析字符串就能针对特定的失败情况做出反应。
下一步:控制台与 DevTools
了解错误类型只是成功了一半,另一半是读懂堆栈跟踪,并在程序运行时查看它的状态。浏览器的开发者工具(以及 Node 的调试器)能把“它报错了,我也不知道为什么”变成几秒钟的检查。下一篇就讲这个。
常见问题
JavaScript 有哪些内置错误类型?
JavaScript 自带七种:Error(基类)、SyntaxError、ReferenceError、TypeError、RangeError、URIError 和 EvalError。每一种都是一个构造函数,生成的错误对象带有 name、message 和 stack 属性。你最常遇到的是 SyntaxError、ReferenceError 和 TypeError。
SyntaxError 和 TypeError 有什么区别?
SyntaxError 表示代码不是合法的 JavaScript,引擎甚至无法解析它。TypeError 表示代码能正常解析,但在运行时做了不合法的操作,比如调用一个不是函数的东西,或者读取 null 的属性。语法错误会让整个脚本停止运行;类型错误只在出问题的那一行执行时才触发。
JavaScript 中什么时候会出现 ReferenceError?
当你使用一个没有声明过的名称,或者在 let/const 变量的暂时性死区内(声明语句执行之前)访问它时。最常见的原因是拼写错误:consoel.log(x) 会抛出 ReferenceError: consoel is not defined。先检查拼写和作用域。
可以创建自己的错误类型吗?
可以。继承内置的 Error 类:class ValidationError extends Error { }。在构造函数中设置 this.name,这样日志和 catch 分支就能区分它。当不同的失败需要不同的处理方式时,自定义错误类就很有价值。