控制流程和错误处理

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

¥JavaScript supports a compact set of statements, specifically control flow statements, that you can use to incorporate a great deal of interactivity in your application. This chapter provides an overview of these statements.

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

¥The JavaScript reference contains exhaustive details about the statements in this chapter. The semicolon (;) character is used to separate statements in JavaScript code.

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

¥Any JavaScript expression is also a statement. See Expressions and operators for complete information about expressions.

块语句

¥Block statement

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

¥The most basic statement is a block statement, which is used to group statements. The block is delimited by a pair of curly braces:

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

示例

¥Example

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

¥Block statements are commonly used with control flow statements (if, for, while).

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

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

¥Here, { x++; } is the block statement.

注意:var 声明的变量不是块作用域的,但作用域是包含函数或脚本的,并且设置它们的效果在块本身之外仍然存在。例如:

¥Note: var-declared variables are not block-scoped, but are scoped to the containing function or script, and the effects of setting them persist beyond the block itself. For example:

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

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

¥This outputs 2 because the var x statement within the block is in the same scope as the var x statement before the block. (In C or Java, the equivalent code would have output 1.)

使用 letconst 可以减轻这种范围效应。

¥This scoping effect can be mitigated by using let or const.

条件语句

¥Conditional statements

条件语句是一组在指定条件为真时执行的命令。JavaScript 支持两种条件语句:if...elseswitch

¥A conditional statement is a set of commands that executes if a specified condition is true. JavaScript supports two conditional statements: if...else and switch.

if...else 语句

¥if...else statement

如果逻辑条件为 true,则使用 if 语句来执行语句。如果条件为 false,则使用可选的 else 子句执行语句。

¥Use the if statement to execute a statement if a logical condition is true. Use the optional else clause to execute a statement if the condition is false.

if 语句如下所示:

¥An if statement looks like this:

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

这里,condition 可以是任何计算结果为 truefalse 的表达式。(有关 truefalse 的计算结果的说明,请参阅 布尔值。)

¥Here, the condition can be any expression that evaluates to true or false. (See Boolean for an explanation of what evaluates to true and false.)

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

¥If condition evaluates to true, statement_1 is executed. Otherwise, statement_2 is executed. statement_1 and statement_2 can be any statement, including further nested if statements.

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

¥You can also compound the statements using else if to have multiple conditions tested in sequence, as follows:

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

如果有多个条件,则仅执行第一个值为 true 的逻辑条件。要执行多个语句,请将它们分组在块语句 ({ /* … */ }) 中。

¥In the case of multiple conditions, only the first logical condition which evaluates to true will be executed. To execute multiple statements, group them within a block statement ({ /* … */ }).

最佳实践

¥Best practice

一般来说,最好始终使用块语句,尤其是在嵌套 if 语句时:

¥In general, it's good practice to always use block statements—especially when nesting if statements:

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

一般来说,最好不要将 if...elsex = y 这样的作业作为条件:

¥In general it's good practice to not have an if...else with an assignment like x = y as a condition:

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

然而,在极少数情况下,你发现自己想要做类似的事情,while 文档有一个 使用赋值作为条件 部分,其中提供了你应该了解并遵循的一般最佳实践语法的指导。

¥However, in the rare case you find yourself wanting to do something like that, the while documentation has a Using an assignment as a condition section with guidance on a general best-practice syntax you should know about and follow.

错误的价值观

¥Falsy values

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

¥The following values evaluate to false (also known as Falsy values):

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

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

¥All other values—including all objects—evaluate to true when passed to a conditional statement.

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

¥Note: Do not confuse the primitive boolean values true and false with the true and false values of the Boolean object!

例如:

¥For example:

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

示例

¥Example

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

¥In the following example, the function checkData returns true if the number of characters in a Text object is three. Otherwise, it displays an alert and returns 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 statement

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

¥A switch statement allows a program to evaluate an expression and attempt to match the expression's value to a case label. If a match is found, the program executes the associated statement.

switch 语句如下所示:

¥A switch statement looks like this:

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

JavaScript 按如下方式评估上述 switch 语句:

¥JavaScript evaluates the above switch statement as follows:

  • 程序首先查找标签与表达式值匹配的 case 子句,然后将控制权转移到该子句,执行关联的语句。
  • 如果没有找到匹配的标签,程序将查找可选的 default 子句:
    • 如果找到 default 子句,程序会将控制权转移到该子句,并执行关联的语句。
    • 如果没有找到 default 子句,则程序从 switch 末尾后面的语句处恢复执行。
    • (按照惯例,default 子句写为最后一个子句,但不一定如此。)

中断语句

¥break statements

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

¥The optional break statement associated with each case clause ensures that the program breaks out of switch once the matched statement is executed, and then continues execution at the statement following switch. If break is omitted, the program continues execution inside the switch statement (and will execute statements under the next case, and so on).

示例

¥Example

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

¥In the following example, if fruitType evaluates to 'Bananas', the program matches the value with case 'Bananas' and executes the associated statement. When break is encountered, the program exits the switch and continues execution from the statement following switch. If break were omitted, the statement for case 'Cherries' would also be executed.

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?");

异常处理语句

¥Exception handling statements

你可以使用 throw 语句引发异常并使用 try...catch 语句处理异常。

¥You can throw exceptions using the throw statement and handle them using the try...catch statements.

异常类型

¥Exception types

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

¥Just about any object can be thrown in JavaScript. Nevertheless, not all thrown objects are created equal. While it is common to throw numbers or strings as errors, it is frequently more effective to use one of the exception types specifically created for this purpose:

抛出语句

¥throw statement

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

¥Use the throw statement to throw an exception. A throw statement specifies the value to be thrown:

js
throw expression;

你可以抛出任何表达式,而不仅仅是特定类型的表达式。以下代码抛出多个不同类型的异常:

¥You may throw any expression, not just expressions of a specific type. The following code throws several exceptions of varying types:

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

try...catch 声明

¥try...catch statement

try...catch 语句标记要尝试的语句块,并指定抛出异常时的一个或多个响应。如果抛出异常,try...catch 语句将捕获它。

¥The try...catch statement marks a block of statements to try, and specifies one or more responses should an exception be thrown. If an exception is thrown, the try...catch statement catches it.

try...catch 语句由 try 块和 catch 块组成,其中 try 块包含一个或多个语句,而 catch 块包含指定在 try 块中引发异常时要执行的操作的语句。

¥The try...catch statement consists of a try block, which contains one or more statements, and a catch block, containing statements that specify what to do if an exception is thrown in the try block.

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

¥In other words, you want the try block to succeed—but if it does not, you want control to pass to the catch block. If any statement within the try block (or in a function called from within the try block) throws an exception, control immediately shifts to the catch block. If no exception is thrown in the try block, the catch block is skipped. The finally block executes after the try and catch blocks execute but before the statements following the try...catch statement.

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

¥The following example uses a try...catch statement. The example calls a function that retrieves a month name from an array based on the value passed to the function. If the value does not correspond to a month number (112), an exception is thrown with the value 'InvalidMonthNo' and the statements in the catch block set the monthName variable to '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)
}

捕获块

¥The catch block

你可以使用 catch 块来处理 try 块中可能生成的所有异常。

¥You can use a catch block to handle all exceptions that may be generated in the try block.

js
catch (exception) {
  statements
}

catch 块指定一个标识符(前面语法中的 exception),该标识符保存 throw 语句指定的值。你可以使用此标识符来获取有关引发的异常的信息。

¥The catch block specifies an identifier (exception in the preceding syntax) that holds the value specified by the throw statement. You can use this identifier to get information about the exception that was thrown.

当输入 catch 块时,JavaScript 创建此标识符。该标识符仅在 catch 块的持续时间内持续。一旦 catch 块执行完毕,标识符就不再存在。

¥JavaScript creates this identifier when the catch block is entered. The identifier lasts only for the duration of the catch block. Once the catch block finishes executing, the identifier no longer exists.

例如,以下代码会引发异常。当异常发生时,控制权转移到 catch 块。

¥For example, the following code throws an exception. When the exception occurs, control transfers to the catch block.

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() 进行调试。它将消息格式化为错误,并将其添加到页面生成的错误消息列表中。

¥Note: When logging errors to the console inside a catch block, using console.error() rather than console.log() is advised for debugging. It formats the message as an error, and adds it to the list of error messages generated by the page.

最后块

¥The finally block

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

¥The finally block contains statements to be executed after the try and catch blocks execute. Additionally, the finally block executes before the code that follows the try…catch…finally statement.

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

¥It is also important to note that the finally block will execute whether or not an exception is thrown. If an exception is thrown, however, the statements in the finally block execute even if no catch block handles the exception that was thrown.

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

¥You can use the finally block to make your script fail gracefully when an exception occurs. For example, you may need to release a resource that your script has tied up.

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

¥The following example opens a file and then executes statements that use the file. (Server-side JavaScript allows you to access files.) If an exception is thrown while the file is open, the finally block closes the file before the script fails. Using finally here ensures that the file is never left open, even if an error occurs.

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 块返回一个值,则该值将成为整个 try…catch…finally 产生式的返回值,无论 trycatch 块中的任何 return 语句如何:

¥If the finally block returns a value, this value becomes the return value of the entire try…catch…finally production, regardless of any return statements in the try and catch blocks:

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 块内部抛出或重新抛出的异常:

¥Overwriting of return values by the finally block also applies to exceptions thrown or re-thrown inside of the catch block:

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 语句

¥Nesting try...catch statements

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

¥You can nest one or more try...catch statements.

如果内部 try 块没有对应的 catch 块:

¥If an inner try block does not have a corresponding catch block:

  1. 它必须包含 finally 块,并且
  2. 检查封闭的 try...catch 语句的 catch 块是否匹配。

有关详细信息,请参阅 try...catch 参考页上的 嵌套的 try 块

¥For more information, see nested try-blocks on the try...catch reference page.

使用错误对象

¥Utilizing Error objects

根据错误的类型,你也许可以使用 namemessage 属性来获取更精确的消息。

¥Depending on the type of error, you may be able to use the name and message properties to get a more refined message.

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

¥The name property provides the general class of Error (such as DOMException or Error), while message generally provides a more succinct message than one would get by converting the error object to a string.

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

¥If you are throwing your own exceptions, in order to take advantage of these properties (such as if your catch block doesn't discriminate between your own exceptions and system ones), you can use the Error constructor.

例如:

¥For example:

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
}