控制流和错误处理

JavaScript 支持一组紧凑的语句,特别是控制流语句,您可以使用它们在应用程序中加入大量交互性。本章概述了这些语句。

JavaScript 参考 包含本章中语句的详尽细节。分号 (;) 字符用于分隔 JavaScript 代码中的语句。

任何 JavaScript 表达式也是一个语句。请参阅 表达式和运算符 获取有关表达式的完整信息。

块语句

最基本的语句是块语句,用于对语句进行分组。块由一对花括号分隔

js
{
  statement1;
  statement2;
  // …
  statementN;
}

示例

块语句通常与控制流语句 (ifforwhile) 一起使用。

js
while (x < 10) {
  x++;
}

这里,{ x++; } 是块语句。

注意: var 声明的变量不是块级作用域,而是包含函数或脚本的作用域,并且设置它们的效果会持续到块本身之外。例如

js
var x = 1;
{
  var x = 2;
}
console.log(x); // 2

这输出 2,因为块内的 var x 语句与块之前的 var x 语句位于同一作用域。(在 C 或 Java 中,等效代码将输出 1。)

可以通过使用 letconst 来缓解这种作用域影响。

条件语句

条件语句是一组命令,如果指定条件为真,则执行这些命令。JavaScript 支持两种条件语句:if...elseswitch

if...else 语句

使用 if 语句在逻辑条件为 true 时执行语句。使用可选的 else 子句在条件为 false 时执行语句。

if 语句如下所示

js
if (condition) {
  statement1;
} else {
  statement2;
}

这里,condition 可以是任何计算结果为 truefalse 的表达式。(请参阅 布尔值 以了解哪些值计算结果为 truefalse。)

如果 condition 计算结果为 true,则执行 statement_1。否则,执行 statement_2statement_1statement_2 可以是任何语句,包括进一步嵌套的 if 语句。

您还可以使用 else if 组合语句,以按顺序测试多个条件,如下所示

js
if (condition1) {
  statement1;
} else if (condition2) {
  statement2;
} else if (conditionN) {
  statementN;
} else {
  statementLast;
}

在多个条件的情况下,只会执行第一个计算结果为 true 的逻辑条件。要执行多个语句,请将它们分组到块语句 ({ /* … */ }) 中。

最佳实践

通常,最好始终使用块语句,尤其是在嵌套 if 语句时

js
if (condition) {
  // Statements for when condition is true
  // …
} else {
  // Statements for when condition is false
  // …
}

通常,最好不要使用 if...else,其中 x = y 作为条件的赋值。

js
if (x = y) {
  // statements here
}

但是,如果您很少发现自己想做类似的事情,则 while 文档有一个 使用赋值作为条件 部分,其中包含有关您应该了解并遵循的一般最佳实践语法的指南。

假值

以下值计算结果为 false(也称为 假值):

  • false
  • undefined
  • null
  • 0
  • NaN
  • 空字符串 ("")

所有其他值(包括所有对象)在传递给条件语句时计算结果都为 true

注意: 不要将原始布尔值 truefalseBoolean 对象的真值和假值混淆!

例如

js
const b = new Boolean(false);
if (b) {
  // this condition evaluates to true
}
if (b == true) {
  // this condition evaluates to false
}

示例

在以下示例中,如果 Text 对象的字符数为三个,则函数 checkData 返回 true。否则,它会显示警报并返回 false

js
function checkData() {
  if (document.form1.threeChar.value.length === 3) {
    return true;
  } else {
    alert(
      `Enter exactly three characters. ${document.form1.threeChar.value} is not valid.`,
    );
    return false;
  }
}

switch 语句

switch 语句允许程序评估表达式并尝试将表达式的值与 case 标签匹配。如果找到匹配项,程序将执行关联的语句。

switch 语句如下所示

js
switch (expression) {
  case label1:
    statements1;
    break;
  case label2:
    statements2;
    break;
  // …
  default:
    statementsDefault;
}

JavaScript 如下评估上述 switch 语句:

  • 程序首先查找标签与 expression 值匹配的 case 子句,然后将控制权转移到该子句,并执行关联的语句。
  • 如果找不到匹配的标签,程序将查找可选的 default 子句
    • 如果找到 default 子句,程序将控制权转移到该子句,并执行关联的语句。
    • 如果找不到 default 子句,程序将在 switch 结束后的语句处恢复执行。
    • (按照惯例,default 子句写为最后一个子句,但它不需要这样。)

break 语句

与每个 case 子句关联的可选 break 语句确保程序在执行匹配的语句后退出 switch,然后在 switch 后的语句处继续执行。如果省略 break,程序将在 switch 语句内继续执行(并将执行下一个 case 下的语句,依此类推)。

示例

在以下示例中,如果 fruitType 计算结果为 'Bananas',则程序将该值与 case 'Bananas' 匹配并执行关联的语句。遇到 break 时,程序退出 switch 并从 switch 后的语句处继续执行。如果省略 break,则还会执行 case 'Cherries' 的语句。

js
switch (fruitType) {
  case "Oranges":
    console.log("Oranges are $0.59 a pound.");
    break;
  case "Apples":
    console.log("Apples are $0.32 a pound.");
    break;
  case "Bananas":
    console.log("Bananas are $0.48 a pound.");
    break;
  case "Cherries":
    console.log("Cherries are $3.00 a pound.");
    break;
  case "Mangoes":
    console.log("Mangoes are $0.56 a pound.");
    break;
  case "Papayas":
    console.log("Mangoes and papayas are $2.79 a pound.");
    break;
  default:
    console.log(`Sorry, we are out of ${fruitType}.`);
}
console.log("Is there anything else you'd like?");

异常处理语句

您可以使用 throw 语句抛出异常,并使用 try...catch 语句处理它们。

异常类型

几乎任何对象都可以在 JavaScript 中抛出。然而,并非所有抛出的对象都是平等的。虽然通常将数字或字符串作为错误抛出,但使用为此目的专门创建的异常类型之一通常更有效

throw 语句

使用 throw 语句抛出异常。throw 语句指定要抛出的值

js
throw expression;

您可以抛出任何表达式,而不仅仅是特定类型的表达式。以下代码抛出几种不同类型的异常

js
throw "Error2"; // String type
throw 42; // Number type
throw true; // Boolean type
throw {
  toString() {
    return "I'm an object!";
  },
};

try...catch 语句

try...catch 语句标记要尝试的一组语句,并指定如果抛出异常则应采取的一种或多种响应。如果抛出异常,try...catch 语句会捕获它。

try...catch 语句由一个 try 块组成,该块包含一个或多个语句,以及一个 catch 块,该块包含指定如果在 try 块中抛出异常该怎么办的语句。

换句话说,您希望 try 块成功,但如果它不成功,您希望控制权传递到 catch 块。如果 try 块中的任何语句(或在从 try 块中调用的函数中)抛出异常,则控制权将立即转移到 catch 块。如果在 try 块中没有抛出异常,则跳过 catch 块。finally 块在 trycatch 块执行后但在 try...catch 语句后的语句之前执行。

以下示例使用 try...catch 语句。该示例调用一个函数,该函数根据传递给函数的值从数组中检索月份名称。如果该值与月份编号 (112) 不对应,则会抛出异常,其值为 'InvalidMonthNo',并且 catch 块中的语句将 monthName 变量设置为 'unknown'

js
function getMonthName(mo) {
  mo--; // Adjust month number for array index (so that 0 = Jan, 11 = Dec)
  const months = [
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
  ];
  if (months[mo]) {
    return months[mo];
  } else {
    throw new Error("InvalidMonthNo"); // throw keyword is used here
  }
}

try {
  // statements to try
  monthName = getMonthName(myMonth); // function could throw exception
} catch (e) {
  monthName = "unknown";
  logMyErrors(e); // pass exception object to error handler (i.e. your own function)
}

catch 块

您可以使用catch块来处理try块中可能生成的任何异常。

js
catch (exception) {
  statements
}

catch块指定了一个标识符(在前面的语法中为exception),该标识符保存由throw语句指定的值。您可以使用此标识符来获取有关已抛出异常的信息。

当进入catch块时,JavaScript 会创建此标识符。该标识符仅在catch块执行期间存在。一旦catch块执行完毕,标识符将不再存在。

例如,以下代码抛出一个异常。当异常发生时,控制权将转移到catch块。

js
try {
  throw "myException"; // generates an exception
} catch (err) {
  // statements to handle any exceptions
  logMyErrors(err); // pass exception object to error handler
}

注意:catch块中将错误记录到控制台时,建议使用console.error()而不是console.log()进行调试。它会将消息格式化为错误,并将其添加到页面生成的错误消息列表中。

finally 块

finally块包含在trycatch块执行之后执行的语句。此外,finally块在try…catch…finally语句之后的代码之前执行。

还需要注意的是,无论是否抛出异常,finally块都将执行。但是,如果抛出异常,即使没有catch块处理已抛出的异常,finally块中的语句也会执行。

您可以使用finally块在发生异常时使脚本优雅地失败。例如,您可能需要释放脚本已占用的资源。

以下示例打开一个文件,然后执行使用该文件的语句。(服务器端 JavaScript 允许您访问文件。)如果在文件打开时抛出异常,则finally块在脚本失败之前关闭文件。在此处使用finally可以确保文件永远不会保持打开状态,即使发生错误也是如此。

js
openMyFile();
try {
  writeMyFile(theData); // This may throw an error
} catch (e) {
  handleError(e); // If an error occurred, handle it
} finally {
  closeMyFile(); // Always close the resource
}

如果finally块返回一个值,则无论trycatch块中的任何return语句如何,此值都将成为整个try…catch…finally结构的返回值。

js
function f() {
  try {
    console.log(0);
    throw "bogus";
  } catch (e) {
    console.log(1);
    // This return statement is suspended
    // until finally block has completed
    return true;
    console.log(2); // not reachable
  } finally {
    console.log(3);
    return false; // overwrites the previous "return"
    console.log(4); // not reachable
  }
  // "return false" is executed now
  console.log(5); // not reachable
}
console.log(f()); // 0, 1, 3, false

finally块覆盖返回值也适用于在catch块内部抛出或重新抛出的异常。

js
function f() {
  try {
    throw "bogus";
  } catch (e) {
    console.log('caught inner "bogus"');
    // This throw statement is suspended until
    // finally block has completed
    throw e;
  } finally {
    return false; // overwrites the previous "throw"
  }
  // "return false" is executed now
}

try {
  console.log(f());
} catch (e) {
  // this is never reached!
  // while f() executes, the `finally` block returns false,
  // which overwrites the `throw` inside the above `catch`
  console.log('caught outer "bogus"');
}

// Logs:
// caught inner "bogus"
// false

嵌套 try...catch 语句

您可以嵌套一个或多个try...catch语句。

如果内部try没有对应的catch

  1. 则它必须包含一个finally块,并且
  2. 检查封闭的try...catch语句的catch块以查找匹配项。

有关更多信息,请参阅嵌套 try 块try...catch参考页面上。

利用 Error 对象

根据错误类型,您可能能够使用namemessage属性来获取更详细的消息。

name属性提供Error的一般类(例如DOMExceptionError),而message通常提供比将错误对象转换为字符串获得的更简洁的消息。

如果您正在抛出自己的异常,为了利用这些属性(例如,如果您的catch块不区分您自己的异常和系统异常),您可以使用Error构造函数。

例如

js
function doSomethingErrorProne() {
  if (ourCodeMakesAMistake()) {
    throw new Error("The message");
  } else {
    doSomethingToGetAJavaScriptError();
  }
}

try {
  doSomethingErrorProne();
} catch (e) {
  // Now, we actually use `console.error()`
  console.error(e.name); // 'Error'
  console.error(e.message); // 'The message', or a JavaScript error message
}