[译] JavaScript中的异常处理

472 阅读7分钟

原文地址:Exceptional Exception Handling in JavaScript

一、所有Error对象

当发生异常时,一个表示错误的对象将创建并抛出。JavaScript语言定义了7种类型的内置错误对象。这些错误类型是异常处理的基础。下面详细描述了每种错误类型。

1、Error 普通异常

“Error”类型用于表示普通的异常。这种类型的异常最常用于实现用户定义的异常。
本文稍后会详细讲解创建用户定义异常这个主题。“Error”对象通过调用其构造函数来实例化,如下面的示例所示:

var error = new Error("error message");

“Error”对象包含两个属性,“name”和“message”。

  • “name”属性指定异常的类型(在本例中为“Error”)。
  • “message”属性提供了异常的更详细描述。“message”从传递给异常构造函数的字符串中获取其值。
    其余的异常类型代表更特定的错误类型,但它们都以与通用的“Error”类型相同的方式使用。

2、RangeError 数字异常错误

" RangeError "异常由超出指定范围的数字生成。例如,JavaScript数字有一个toFixed()方法,它接受一个“digits”参数,表示小数点后出现的位数。这个参数预计在0到20之间(尽管有些浏览器支持更大的范围)。如果" digits "的值在此范围之外,则抛出" RangeError "。这个场景在下面的示例中显示。

var pi = 3.14159;

pi.toFixed(100000);  // RangeError

3、ReferenceError 变量不存在

当访问不存在的变量时,会抛出一个“ReferenceError”异常。这些异常通常发生在现有变量名称拼写错误时。在下面的例子中,当“bar”被访问时,会出现“ReferenceError”。注意,此示例假设在尝试递增操作时,“bar”不存在于任何活动范围中。

function foo() {
  bar++;  // ReferenceError
}

4、SyntaxError 语法错误异常

当JavaScript语言的规则被打破时,抛出一个“SyntaxError”。熟悉C和Java等语言的开发人员经常会在编译过程中遇到语法错误。但是,由于JavaScript是一种解释语言,语法错误直到执行代码时才会被识别。语法错误是唯一的,因为它们是唯一无法恢复的异常类型。下面的示例生成了一个语法错误,因为“if”语句缺少右花括号。

if (foo) {  // SyntaxError
  // the closing curly brace is missing

5、TypeError

当值不是期望的类型时,会发生" TypeError "异常。试图调用不存在的对象方法是导致这种类型异常的常见原因。下面的例子创建了一个名为“foo”的空对象,然后试图调用它的bar()方法。由于bar()未定义,因此在尝试调用时抛出" TypeError "。

var foo = {};

foo.bar(); // TypeError

6、URIError

当encodeURI()和decodeURI()等方法遇到畸形的URI时,会抛出“URIError”异常。下面的例子在试图解码字符串“%”时生成了一个“URIError”。字符“%”表示URI转义序列的开始。由于在本例中“%”后面没有任何内容,因此该字符串是一个无效的转义序列,因此是一个畸形的URI组件。

decodeURIComponent("%"); // URIError

7、EvalError

当eval()函数使用不当时,将引发" EvalError "异常。这些异常在EcmaScript标准的最新版本中没有使用。但是,它们仍然受到支持,以保持与老版本标准的向后兼容性。

二、处理异常

js中使用try……catch……finally来处理异常

try {
  // attempt to execute this code
} catch (exception) {
  // this code handles exceptions
} finally {
  // this code always gets executed
}

“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 "子句中获得。

try {
  foo++;  // ReferenceError
} catch (exception) {
  var message = exception.message;

  // handle the exception
}

复杂的应用程序可以生成各种各样的异常。在这种情况下,可以使用“instanceof”操作符来区分各种类型的异常。在下面的例子中,假设“try”子句可以生成几种类型的异常。相应的" catch "子句使用" instanceof "来单独处理" TypeError "和" ReferenceError "异常,而不是所有其他类型的错误。

try {
  // assume an exception occurs
} catch (exception) {
  if (exception instanceof TypeError) {
    // Handle TypeError exceptions
  } else if (exception instanceof ReferenceError) {
    // Handle ReferenceError exceptions
  } else {
    // Handle all other types of exceptions
  }
}

finally子句

“finally”子句不管是否有错误,都会在“try”和“catch”子句之后执行,“finally”子句对于包含无论如何都需要执行的清理代码(关闭文件等)很有用。

注意,如果出现未捕获的异常,则实际上仍然会执行" finally "子句。在这种情况下,执行“finally”子句后抛出的异常正常继续。

关于“finally”子句的一个有趣的注意事项是,即使“try”或“catch”子句执行了“return”语句,它也会被执行。例如,下面的函数返回false,因为" finally "子句是最后执行的。

function foo() {
  try {
    return true;
  } finally {
    return false;
  }
}

三、抛出异常 throw

JavaScript允许程序员通过适当命名的“throw”语句抛出自己的异常。这个概念可能会让没有经验的开发人员感到困惑。毕竟,开发人员努力编写没有错误的代码,而“throw”语句却有意地引入了错误。然而,有意地抛出异常实际上会更容易调试和维护的代码。例如,通过创建有意义的错误消息,可以更容易地识别和解决问题。

下面显示了几个“throw”语句的示例。对于可以作为异常抛出的数据类型没有任何限制。同样的数据可以被捕获和抛出的次数也没有限制。换句话说,异常可以被抛出、捕获,然后再次抛出。

throw true;
throw 5;
throw "error message";
throw null;
throw undefined;
throw {};
throw new SyntaxError("useful error message");

虽然“throw”语句可以用于任何数据类型,但使用内置异常类型有一些好处。例如,Firefox通过添加诸如发生异常的文件名和行号等调试信息来对这些对象进行特殊处理。

作为一个示例场景,假设在应用程序的某个地方发生了除法操作。除法很麻烦,因为可能被零除。在JavaScript中,这样的操作会导致“NaN”。这可能会导致难以调试的混乱结果。如果应用程序大声抱怨被零除法,事情会简单得多。下面的“if”语句通过抛出异常为我们完成了这一任务。

if (denominator === 0)
  throw new Error("Attempted division by zero!");

当然,使用如下所示的“RangeError”可能更合适。

if (denominator === 0)
  throw new RangeError("Attempted division by zero!");

四、自定义异常对象

我们刚刚学习了如何使用内置异常类型生成定制的错误消息。然而,另一种方法是通过扩展现有的“Error”类型来创建新的异常类型。因为新类型继承自" Error ",所以可以像其他内置异常类型一样使用它。虽然JavaScript中的继承主题超出了本文的范围,但本文将介绍一种简单的技术。

下面的例子返回到处理被零除法的问题。我们将创建自己的异常类型,而不是像前面那样使用“Error”或“RangeError”对象。在本例中,我们创建了异常类型“DivisionByZeroError”。示例中的函数充当新类型的构造函数。构造函数负责分配“name”和“message”属性。示例的最后两行导致新类型从“Error”对象继承。

function DivisionByZeroError(message) {
  this.name = "DivisionByZeroError";
  this.message = (message || "");
}

DivisionByZeroError.prototype = new Error();
DivisionByZeroError.prototype.constructor = DivisionByZeroError;

需要记住的:

  • “try…catch…finally”语句用于处理异常。
  • “try”子句标识可能产生异常的代码。
  • “catch”子句只在异常发生时执行。
  • 不管怎样,“finally”子句总是被执行。
  • “throw”语句用于生成异常。
  • 自定义异常对象应该继承自现有的“Error”类型。