控制流与错误处理
JavaScript 支持一套小巧的语句,特别是控制流语句,你可以用它在你的应用程序中实现大量的交互性功能。本章将对这些语句进行概览。
在本章中涉及的语句,JavaScript 参考包含更为详尽的细节。在 JavaScript 代码中,分号(;
)字符被用来分割语句。
JavaScript 表达式也是语句。想要了解有关表达式的完整信息,参见表达式与运算符。
块语句
最基本的语句是用于组合语句的块语句。块由一对花括号界定:
{ statement1; statement2; // … statementN;}
示例
条件语句
条件语句是一组会在指定的条件为真时执行的指令。JavaScript 支持两种条件语句:if...else
和switch
。
if...else 语句
使用if
语句在逻辑条件为true
时执行语句。使用可选的else
子句在条件为false
时执行语句。
if
语句看起来像这样:
if (condition) { statement1;} else { statement2;}
这里,condition
可以是任何能求值为true
或false
的表达式。(想要了解求值为true
和false
的解释,参见布尔值。)
如果condition
求值为true
,执行statement1
。否则,执行statement2
。statement1
和statement2
可以是任何的语句,包括继续嵌套的if
语句。
你也可以使用else if
组合语句按顺序测试多个条件,就像下面一样:
if (condition1) { statement1;} else if (condition2) { statement2;} else if (conditionN) { statementN;} else { statementLast;}
在多个条件的情况下,只有第一个求值为true
的逻辑条件才会被执行。想要执行多个语句,将其组合在一个块语句中({ /* … */ }
)。
最佳实践
一般而言,总是使用块语句是最佳实践——特别是嵌套if
语句的时候:
if (condition) { // condition 为真时的语句 // …} else { // condition 为假时的语句 // …}
一般而言,最好不要将赋值(例如,x = y
)作为if...else
的条件:
if (x = y) { // 在这里添加语句}
然而,在极少数情况下,你会发现自己想要这么做,while
文档中的使用赋值作为条件小节是你应该了解并遵循的通用的最佳实践语法指南。
假值
下面这些值求值为false
(也叫做假值):
false
undefined
null
0
NaN
- 空字符串(
""
)
所有其他的值——包括所有的对象——在被传递给条件语句时会求值为true
。
备注:请不要混淆原始布尔值true
和false
与Boolean
对象的真和假!
例如:
const b = new Boolean(false);if (b) { // 这个条件求值为 true}if (b == true) { // 这个条件求值为 false}
示例
在下列示例中,如果Text
对象中的字符数为 3,函数checkData
将返回true
;否则,显示警告并返回false
。
function checkData() { if (document.form1.threeChar.value.length === 3) { return true; } else { alert(`请正好输入 3 个字符。${document.form1.threeChar.value}是无效的`); return false; }}
switch 语句
switch
语句允许程序求一个表达式的值并且尝试将表达式的值和case
标签进行匹配。如果匹配成功,程序会执行相关的语句。
switch
语句看起来像这样:
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"
下的语句。
switch (fruitType) { case "Oranges": console.log("橙子是 $0.59 一磅"); break; case "Apples": console.log("苹果是 $0.32 一磅"); break; case "Bananas": console.log("香蕉是 $0.48 一磅"); break; case "Cherries": console.log("樱桃是 $3.00 一磅"); break; case "Mangoes": console.log("芒果是 $0.56 一磅。"); break; case "Papayas": console.log("木瓜是 $2.79 一磅。"); break; default: console.log(`对不起,${fruitType} 卖完了。`);}console.log("还有其他什么是你喜欢的吗?");
异常处理语句
你可以用throw
语句抛出一个异常并且用try...catch
语句处理它。
异常类型
JavaScript 可以抛出任意对象。然而,并非所有抛出的对象都是生而平等的。尽管抛出数字或者字符串作为错误信息十分常见,但是用下列其中一种专门为这个目的而创建的异常类型通常更为高效:
throw 语句
使用throw
语句抛出异常。throw
语句会指明要抛出的值:
throw expression;
你可以抛出任意表达式而不是特定类型的表达式。下面的代码抛出了几个不同类型的异常:
throw "错误 2"; // 字符串类型throw 42; // 数字类型throw true; // 布尔类型throw { toString() { return "我是一个对象"; },};
try...catch 语句
try...catch
语句用于标记一段要尝试的语句块,并指定抛出异常时的一个或多个响应。如果抛出了异常,try...catch
语句会捕获它。
try...catch
语句由try
块(其包含一个或多个语句)和catch
块(其包含在try
块中抛出异常时要执行的语句)组成。
换句话说,你希望try
块执行成功——但如果它没有执行成功,那么你希望将控制转移到catch
块。如果try
块中的语句(或者在try
块中调用的函数里)抛出了异常,那么控制立马转移到catch
块。如果try
块没有抛出异常,catch
块就会被跳过。finally
块总会紧跟在try
和catch
块之后执行,但会在try...catch
语句后面的语句之前执行。
下列示例使用了try...catch
语句。示例调用的函数是根据传递的值从数组中获取一个月份名称。如果该值与月份数值(1
-12
)不相符,会抛出带有"无效的月份数值"
值的异常,然后在catch
块的语句中设monthName
变量为"未知"
。
function getMonthName(mo) { mo--; // 将月份调整为数组索引(这样的话,0 = 一月,11 = 十二月) const months = [ "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", ]; if (months[mo]) { return months[mo]; } else { throw new Error("无效的月份数值"); // 在这使用 throw 关键字 }}try { // 要尝试的语句 monthName = getMonthName(myMonth); // 可能抛出异常的函数} catch (e) { monthName = "未知"; logMyErrors(e); // 将异常对象传递给错误处理器(例如,你写的函数)}
catch 块
你可以使用catch
块来处理所有可能在try
块中产生的异常。
catch (exception) { statements}
catch
块指定的标识符(上述语句中的exception
)会存储由throw
语句指定的值。你可以用这个标识符来获取抛出的异常的信息。在进入catch
块时 JavaScript 会创建这个标识符。标识符只存在于catch
块的存续期间里。当catch
块执行完成时,标识符不再可用。
举个例子,下面代码抛出了一个异常。当异常出现时控制会转移到catch
块。
try { throw "我的异常"; // 生成一个异常} catch (err) { // 处理异常的表达式 logMyErrors(err); // 将异常对象传递给错误处理器}
备注:在catch
块中将错误输出到控制台时,建议使用console.error()
而不是console.log()
进行调试。它会将消息格式化为错误,并将其添加到页面生成的错误消息列表。
finally 块
finally
块包含的语句在try
和catch
块执行之后执行。此外,finally
块在try…catch…finally
语句后面的代码之前执行。
也应该注意,finally
块无论是否抛出异常都会执行。如果抛出了一个异常,就算没有catch
块处理抛出的异常,finally
块里的语句也会执行。
你可以用finally
块来令你的脚本在异常发生时优雅地退出。举个例子,你可能需要释放脚本绑定的资源。
下列的示例打开一个文件,然后执行使用这个文件的语句。(服务器端 JavaScript 允许你访问文件。)如果在文件打开时抛出异常,finally
块会在脚本失败之前关闭文件。在这使用finally
能确保文件永远不会是打开状态,即使发生了错误。
openMyFile();try { writeMyFile(theData); // 这可能会抛出错误} catch (e) { handleError(e); // 如果错误出现了,处理它} finally { closeMyFile(); // 总是关闭资源}
如果finally
块返回一个值,该值会是整个try…catch…finally
流程的返回值,不管在try
和catch
块中的return
语句返回了什么:
function f() { try { console.log(0); throw "bogus"; } catch (e) { console.log(1); // 这个 return 语句会被挂起直到 finally 块结束 return true; console.log(2); // 不可达 } finally { console.log(3); return false; // 覆盖前面的“return” console.log(4); // 不可达 } // 现在执行“return false” console.log(5); // 不可达}console.log(f()); // 0、1、3、false
用finally
块覆盖返回值也适用于在catch
块内抛出或重新抛出的异常:
function f() { try { throw "bogus"; } catch (e) { console.log("捕获内部的“bogus”"); // 这个 return 语句会被挂起直到 finally 块结束 throw e; } finally { return false; // 覆盖前面的“throw” } // 现在执行“return false”}try { console.log(f());} catch (e) { // 这永远不会抵达! // f() 执行时,`finally` 块返回 false,而这会覆盖上面的 `catch` 中的 `throw` console.log("捕获外部的“bogus”");}// 日志:// 捕获内部的“bogus”// false
嵌套 try...catch 语句
你可以嵌套一个或多个try ... catch
语句。
如果一个内部try
块没有对应的catch
块:
- 它必须有一个
finally
块,以及 - 包围的
try...catch
语句的catch
块会被检查是否能处理该异常。
想要了解更多信息,参见try... catch
参考页上的嵌套 try 块。
使用 Error 对象
根据错误类型,你也许可以用name
和message
属性获取更精炼的信息。
name
属性提供了常规的Error
类(如DOMException
或Error
),而message
通常提供的信息比将错误对象转换成字符串得到的信息更简明。
在抛出自定义异常时,为了充分利用那些属性(比如catch
块不能分辨是自定义异常还是系统异常时),你可以使用Error
构造函数。
比如:
function doSomethingErrorProne() { if (ourCodeMakesAMistake()) { throw new Error("消息"); } else { doSomethingToGetAJavaScriptError(); }}try { doSomethingErrorProne();} catch (e) { // 现在,实际使用 `console.error()` console.error(e.name); // 'Error' console.error(e.message); // “消息”,或者一个 JavaScript 错误消息}