核心要点
软件开发中,墨菲定律同样适用:任何可能出错的事情,都会出错。对于非简单的程序,问题不是是否会出错,而是何时会出错。标准不兼容、不支持的功能和浏览器特性只是 web 开发人员面临的潜在问题的几个来源。鉴于可能出现的所有问题,JavaScript 却拥有一个令人惊讶的简单错误处理方法——它只是放弃并默默失败。至少,这是用户看到的行为。实际上,幕后发生了很多事情。
当 JavaScript 语句产生错误时,据说它抛出了一个异常。JavaScript 解释器不会继续执行下一条语句,而是检查异常处理代码。如果没有异常处理程序,则程序将从抛出异常的任何函数返回。这将对调用堆栈上的每个函数重复进行,直到找到异常处理程序或到达顶级函数,导致程序终止。
错误对象
发生异常时,会创建一个表示错误的对象并将其抛出。JavaScript 语言定义了七种类型的内置错误对象。这些错误类型是异常处理的基础。下面详细描述每种错误类型。
“Error” 类型用于表示通用异常。此异常类型最常用于实现用户定义的异常。本文稍后将重新讨论创建用户定义异常的主题。“Error” 对象通过调用其构造函数来实例化,如下例所示:
<code class="language-javascript">var error = new Error("error message");</code>
“Error” 对象包含两个属性,“name” 和“message”。“name” 属性指定异常类型(在本例中为“Error”)。“message” 属性提供对异常的更详细描述。“message” 从传递给异常构造函数的字符串中获取其值。其余异常类型表示更具体的错误类型,但它们的使用方式与通用“Error” 类型相同。
当数字超出指定范围时,会生成“RangeError” 异常。例如,JavaScript 数字具有 toFixed() 方法,该方法采用“digits” 参数,表示小数点后出现的数字位数。此参数应在 0 到 20 之间(尽管某些浏览器支持更宽的范围)。如果“digits” 的值超出此范围,则会抛出“RangeError”。以下示例显示了这种情况。
<code class="language-javascript">var error = new Error("error message");</code>
当访问不存在的变量时,会抛出“ReferenceError” 异常。这些异常通常发生在拼写错误的现有变量名时。在下面的示例中,当访问“bar”时会发生“ReferenceError”。请注意,此示例假设在尝试递增操作时,“bar” 不存在于任何活动作用域中。
<code class="language-javascript">var pi = 3.14159; pi.toFixed(100000); // RangeError</code>
当违反 JavaScript 语言规则时,会抛出“SyntaxError”。熟悉 C 和 Java 等语言的开发人员习惯于在编译过程中遇到语法错误。但是,由于 JavaScript 是一种解释型语言,因此直到执行代码时才会识别语法错误。语法错误是独一无二的,因为它们是唯一一种无法从中恢复的异常类型。以下示例会生成语法错误,因为“if” 语句缺少右花括号。
<code class="language-javascript">function foo() { bar++; // ReferenceError }</code>
当值不是预期类型时,会发生“TypeError” 异常。尝试调用不存在的对象方法是此类异常的常见原因。以下示例创建一个名为“foo”的空对象,然后尝试调用其 bar() 方法。由于未定义 bar(),因此在尝试调用时会抛出“TypeError”。
<code class="language-javascript">if (foo) { // SyntaxError // 缺少右花括号 }</code>
当 encodeURI() 和 decodeURI() 等方法遇到格式错误的 URI 时,会抛出“URIError” 异常。以下示例在尝试解码字符串“%”时会生成“URIError”。“%” 字符表示 URI 转义序列的开头。由于在此示例中“%”之后没有任何内容,因此该字符串是无效的转义序列,因此是格式错误的 URI 组件。
<code class="language-javascript">var foo = {}; foo.bar(); // TypeError</code>
当 eval() 函数使用不当时,会抛出“EvalError” 异常。在最新版本的 EcmaScript 标准中未使用这些异常。但是,为了保持与旧版本标准的向后兼容性,它们仍然受支持。
处理异常
既然我们知道了异常是什么,那么现在就该学习如何阻止它们导致程序崩溃。JavaScript 通过“try…catch…finally”语句处理异常。下面显示了一个通用的示例语句。
<code class="language-javascript">decodeURIComponent("%"); // URIError</code>
“try…catch…finally” 语句的第一部分是“try” 子句。“try” 子句是必需的,用于分隔程序员怀疑可能产生异常的代码块。“try” 子句后面必须跟有一个或两个“catch” 和“finally” 子句。
“catch” 子句
“try…catch…finally” 的第二部分是“catch” 子句。“catch” 子句是一段仅在“try” 子句中发生异常时才执行的代码块。尽管“catch” 子句是可选的,但如果没有它,就不可能真正处理异常。这是因为“catch” 子句阻止异常在调用堆栈中传播,从而允许程序恢复。如果“try” 块中发生异常,则控制权会立即传递给“catch” 子句。发生的异常也会传递到“catch” 块以进行处理。以下示例显示了如何使用“catch” 子句来处理“ReferenceError”。请注意,“ReferenceError” 对象可通过“exception” 变量在“catch” 子句中使用。
<code class="language-javascript">var error = new Error("error message");</code>
复杂的应用程序可以生成各种异常。在这种情况下,可以使用“instanceof” 运算符来区分各种类型的异常。在以下示例中,假设“try” 子句可以生成几种类型的异常。相应的“catch” 子句使用“instanceof” 将“TypeError” 和“ReferenceError” 异常与所有其他类型的错误分开处理。
<code class="language-javascript">var pi = 3.14159; pi.toFixed(100000); // RangeError</code>
“finally” 子句
“try…catch…finally” 语句的最后一个组件是可选的“finally” 子句。“finally” 子句是一段在“try” 和“catch” 子句之后执行的代码块,无论是否出现错误。“finally” 子句对于包含无论如何都需要执行的清理代码(关闭文件等)很有用。请注意,即使发生未捕获的异常,“finally” 子句也会执行。在这种情况下,“finally” 子句会执行,然后抛出的异常会正常继续。
关于“finally” 子句的一个有趣的说明是,即使“try” 或“catch” 子句执行“return” 语句,它也会执行。例如,以下函数返回 false,因为“finally” 子句是最后执行的内容。
<code class="language-javascript">function foo() { bar++; // ReferenceError }</code>
抛出异常
JavaScript 允许程序员通过名为“throw” 的语句抛出自己的异常。这个概念对于没有经验的开发人员来说可能有点令人困惑。毕竟,开发人员努力编写没有错误的代码,但是“throw” 语句却故意引入错误。但是,故意抛出异常实际上可以使代码更易于调试和维护。例如,通过创建有意义的错误消息,可以更容易地识别和解决问题。
下面显示了“throw” 语句的几个示例。对可以作为异常抛出的数据类型没有限制。对可以捕获和抛出相同数据的次数也没有限制。换句话说,可以抛出异常,捕获异常,然后再次抛出异常。
<code class="language-javascript">if (foo) { // SyntaxError // 缺少右花括号 }</code>
虽然“throw” 语句可以与任何数据类型一起使用,但使用内置异常类型具有一定的优势。例如,Firefox 通过添加调试信息(例如发生异常的文件名和行号)来对这些对象进行特殊处理。
例如,假设您的应用程序中的某个地方发生了除法运算。由于可能出现除以零的情况,因此除法可能很麻烦。在 JavaScript 中,此类操作会导致“NaN”。这可能导致难以调试的令人困惑的结果。如果应用程序大声抱怨除以零,情况会简单得多。以下“if” 语句通过抛出异常来实现这一点。
<code class="language-javascript">var error = new Error("error message");</code>
当然,使用“RangeError” 可能更合适,如下所示:
<code class="language-javascript">var pi = 3.14159; pi.toFixed(100000); // RangeError</code>
自定义异常对象
我们刚刚学习了如何使用内置异常类型生成自定义错误消息。但是,另一种方法是通过扩展现有的“Error” 类型来创建新的异常类型。由于新类型继承自“Error”,因此它可以像其他内置异常类型一样使用。虽然本文不讨论 JavaScript 中的继承主题,但这里介绍了一种简单的技术。
以下示例将回到处理除以零的问题。我们不再像前面那样使用“Error” 或“RangeError” 对象,而是要创建我们自己的异常类型。在此示例中,我们正在创建“DivisionByZeroError” 异常类型。示例中的函数充当我们新类型的构造函数。构造函数负责分配“name” 和“message” 属性。示例的最后两行使新类型继承自“Error” 对象。
<code class="language-javascript">function foo() { bar++; // ReferenceError }</code>
需要记住的事项
图片来自 Fotolia
JavaScript 异常处理常见问题解答
语法错误,也称为解析错误,在传统的编程语言中发生在编译时,在 JavaScript 中发生在解释时。当代码在语法上不正确且无法解析时,就会发生语法错误。例如,如果您忘记了右括号或使用了非法字符,则会发生语法错误。
另一方面,运行时错误发生在程序执行期间,在成功编译之后。这些错误通常是由于代码执行的非法操作造成的。例如,尝试访问未定义的变量或调用不存在的函数会导致运行时错误。
JavaScript 提供 try…catch 语句来处理异常。try 块包含可能抛出异常的代码,而 catch 块包含如果 try 块中抛出异常将执行的代码。这是一个简单的示例:
<code class="language-javascript">var error = new Error("error message");</code>
JavaScript 中的“finally” 块用于在 try 和 catch 块之后执行代码,无论结果如何。这意味着无论是否抛出异常,“finally” 块中的代码都会执行。它通常用于在代码执行完毕后进行清理,例如关闭文件或清除资源。
是的,JavaScript 允许您使用“throw” 语句抛出自己的异常。您可以抛出任何类型的异常,包括字符串、数字、布尔值或对象。一旦抛出异常,程序的正常流程就会停止,控制权就会传递给最近的异常处理程序。
JavaScript 允许您通过扩展内置的 Error 对象来创建自定义错误类型。当您想要在代码中抛出特定类型的错误时,这很有用。这是一个示例:
<code class="language-javascript">var pi = 3.14159; pi.toFixed(100000); // RangeError</code>
错误传播是指将错误从抛出它的位置向上传递到调用堆栈中最近的异常处理程序的过程。如果在抛出错误的函数中没有捕获错误,它将向上传播到调用函数,依此类推,直到被捕获或到达全局范围,此时程序将终止。
JavaScript 提供 console.error() 方法来记录错误。此方法的工作方式与 console.log() 相同,但它还在日志中包含堆栈跟踪,并在控制台中将消息突出显示为错误。这是一个示例:
<code class="language-javascript">function foo() { bar++; // ReferenceError }</code>
在 JavaScript 中,“null” 和“undefined” 都表示值的缺失。但是,它们在略微不同的上下文中使用。“Undefined” 表示已声明变量但尚未为其赋值。另一方面,“null” 是一个赋值值,表示没有值或没有对象。
可以使用 JavaScript 中的 Promise 或 async/await 来处理异步错误。Promise 具有一个“catch” 方法,当 Promise 被拒绝时会调用该方法。使用 async/await,您可以像使用同步代码一样使用 try/catch 块。这是一个使用 async/await 的示例:
<code class="language-javascript">if (foo) { // SyntaxError // 缺少右花括号 }</code>
当无法执行操作时,通常是当值不是预期类型时,就会在 JavaScript 中抛出 TypeError。例如,尝试调用非函数或访问未定义的属性会导致 TypeError。
以上是JavaScript中的出色异常处理的详细内容。更多信息请关注PHP中文网其他相关文章!