## 21.控制流语句
> 原文: [http://exploringjs.com/impatient-js/ch_control-flow.html](http://exploringjs.com/impatient-js/ch_control-flow.html)
>
> 贡献者:[飞龙](https://github.com/wizardforcel)
本章介绍以下控制流语句:
* `if`语句(ES1)
* `switch`语句(ES3)
* `while`循环(ES1)
* `do-while`循环(ES3)
* `for`循环(ES1)
* `for-of`循环(ES6)
* `for-await-of`循环(ES2018)
* `for-in`循环(ES1)
在我们得到实际的控制流语句之前,让我们看看两个用于控制循环的运算符。
### 21.1。控制循环:`break`和`continue`
当您在循环中时,两个运算符`break`和`continue`可用于控制循环和其他语句。
#### 21.1.1。 `break`
`break`有两个版本:一个带有操作数,另一个没有操作数。后一版本在以下语句中起作用:`while`,`do-while`,`for`,`for-of`,`for-await-of`,`for-in`和`switch`。它立即离开当前的语句:
```js
for (const x of ['a', 'b', 'c']) {
console.log(x);
if (x === 'b') break;
console.log('---')
}
// Output:
// 'a'
// '---'
// 'b'
```
#### 21.1.2。 `break`的附加用例:离开块
带有操作数的`break`随处可见。其操作数是*标签*。标签可以放在任何语句之前,包括块。 `break foo`离开标签为`foo`的语句:
```js
foo: { // label
if (condition) break foo; // labeled break
// ···
}
```
如果您使用循环并希望区分找到您要查找的内容并完成循环,以及没有成功,则从块中断会偶尔会很方便:
```js
function search(stringArray, suffix) {
let result;
search_block: {
for (const str of stringArray) {
if (str.endsWith(suffix)) {
// Success
result = str;
break search_block;
}
} // for
// Failure
result = '(Untitled)';
} // search_block
return { suffix, result };
// same as: {suffix: suffix, result: result}
}
assert.deepEqual(
search(['foo.txt', 'bar.html'], '.html'),
{ suffix: '.html', result: 'bar.html' }
);
assert.deepEqual(
search(['foo.txt', 'bar.html'], '.js'),
{ suffix: '.js', result: '(Untitled)' }
);
```
#### 21.1.3。 `continue`
`continue`仅适用于`while`,`do-while`,`for`,`for-of`,`for-await-of`和`for-in`。它立即离开当前循环并继续下一个循环。例如:
```js
const lines = [
'Normal line',
'# Comment',
'Another normal line',
];
for (const line of lines) {
if (line.startsWith('#')) continue;
console.log(line);
}
// Output:
// 'Normal line'
// 'Another normal line'
```
### 21.2。 `if`语句
这是两个简单的`if`语句:一个只有一个`then`分支,一个带有`then`分支和一个`else`分支:
```js
if (cond) {
// then branch
}
if (cond) {
// then branch
} else {
// else branch
}
```
`else`也可以跟随另一个`if`语句,而不是块:
```js
if (cond1) {
// ···
} else if (cond2) {
// ···
}
if (cond1) {
// ···
} else if (cond2) {
// ···
} else {
// ···
}
```
您可以使用更多`else if`来继续这个链条。
#### 21.2.1。 `if`语句的语法
`if`语句的一般语法是:
```js
if (cond) «then_statement»
else «else_statement»
```
到目前为止,`then_statement`一直是一个块,但你也可以使用一个语句。该语句必须以分号结束:
```js
if (true) console.log('Yes'); else console.log('No');
```
这意味着`else if`不是独立的构造,它只是一个`if`语句,其`else_statement`是另一个`if`语句。
### 21.3。 `switch`语句
`switch`语句的头部如下所示:
```js
switch («switch_expression») {
«switch_body»
}
```
在`switch`的正文内部,有零个或多个 case 子句:
```js
case «case_expression»:
«statements»
```
并且默认子句是可选的:
```js
default:
«statements»
```
`switch`执行如下:
* 求值`switch`表达式。
* 跳转到第一个`case`子句,其表达式与`switch`表达式结果相同。
* 如果没有这样的`case`子句,请跳转到`default`子句。
* 如果没有默认子句,则不会发生任何事情。
#### 21.3.1。第一个例子
让我们看一个例子:以下函数将一个数字从 1-7 转换为工作日的名称。
```js
function dayOfTheWeek(num) {
switch (num) {
case 1:
return 'Monday';
case 2:
return 'Tuesday';
case 3:
return 'Wednesday';
case 4:
return 'Thursday';
case 5:
return 'Friday';
case 6:
return 'Saturday';
case 7:
return 'Sunday';
}
}
assert.equal(dayOfTheWeek(5), 'Friday');
```
#### 21.3.2。别忘了`return`或`break`!
在 case 子句的末尾,继续执行下一个`case`子句(除非你`return`或`break`)。例如:
```js
function dayOfTheWeek(num) {
let name;
switch (num) {
case 1:
name = 'Monday';
case 2:
name = 'Tuesday';
case 3:
name = 'Wednesday';
case 4:
name = 'Thursday';
case 5:
name = 'Friday';
case 6:
name = 'Saturday';
case 7:
name = 'Sunday';
}
return name;
}
assert.equal(dayOfTheWeek(5), 'Sunday'); // not 'Friday'!
```
也就是说,`dayOfTheWeek()`的先前实现起了作用,因为我们使用了`return`。我们可以使用`break`修复此实现:
```js
function dayOfTheWeek(num) {
let name;
switch (num) {
case 1:
name = 'Monday';
break;
case 2:
name = 'Tuesday';
break;
case 3:
name = 'Wednesday';
break;
case 4:
name = 'Thursday';
break;
case 5:
name = 'Friday';
break;
case 6:
name = 'Saturday';
break;
case 7:
name = 'Sunday';
break;
}
return name;
}
assert.equal(dayOfTheWeek(5), 'Friday');
```
#### 21.3.3。空`case`子句
可以省略`case`子句的语句,这有效地为每个`case`子句提供了多个`case`表达式:
```js
function isWeekDay(name) {
switch (name) {
case 'Monday':
case 'Tuesday':
case 'Wednesday':
case 'Thursday':
case 'Friday':
return true;
case 'Saturday':
case 'Sunday':
return false;
}
}
assert.equal(isWeekDay('Wednesday'), true);
assert.equal(isWeekDay('Sunday'), false);
```
#### 21.3.4。通过`default`子句检查非法值
如果`switch`表达式没有其他匹配项,则跳转到`default`子句。这使它对错误检查很有用:
```js
function isWeekDay(name) {
switch (name) {
case 'Monday':
case 'Tuesday':
case 'Wednesday':
case 'Thursday':
case 'Friday':
return true;
case 'Saturday':
case 'Sunday':
return false;
default:
throw new Error('Illegal value: '+name);
}
}
assert.throws(
() => isWeekDay('January'),
{message: 'Illegal value: January'});
```
![](https://img.kancloud.cn/3e/d5/3ed5755d562179ae6c199264f5e21157.svg) **练习:`switch`**
* `exercises/control-flow/number_to_month_test.js`
* 奖金:`exercises/control-flow/is_object_via_switch_test.js`
### 21.4。 `while`循环
`while`循环具有以下语法:
```js
while («condition») {
«statements»
}
```
在每次循环之前,`while`求值`condition`:
* 如果结果是假值,则循环结束。
* 如果结果是真值,则`while`主体再次执行。
#### 21.4.1。例子
以下代码使用`while`循环。在每次循环中,它通过`.shift()`删除`arr`的第一个元素并记录它。
```js
const arr = ['a', 'b', 'c'];
while (arr.length > 0) {
const elem = arr.shift(); // remove first element
console.log(elem);
}
// Output:
// 'a'
// 'b'
// 'c'
```
如果条件是`true`,则`while`是无限循环:
```js
while (true) {
if (Math.random() === 0) break;
}
```
### 21.5。 `do-while`循环
`do-while`循环的工作原理与`while`非常相似,但它会在每次循环之后(之前)检查其条件。
```js
let input;
do {
input = prompt('Enter text:');
} while (input !== ':q');
```
### 21.6。 `for`循环
对于`for`循环,您可以使用头部控制其主体的执行方式。头部有三个部分,每个部分都是可选的:
```js
for («initialization»; «condition»; «post_iteration») {
«statements»
}
```
* `initialization`:为循环设置变量等。此处通过`let`或`const`语句的变量仅存在于循环内。
* `condition`:在每次循环之前检查此条件。如果是假值,循环就会停止。
* `post_iteration`:此代码在每次循环之后执行。
因此,`for`循环大致等同于以下`while`循环:
```js
«initialization»
while («condition») {
«statements»
«post_iteration»
}
```
#### 21.6.1。例子
例如,这是如何通过`for`循环从零计数到二:
```js
for (let i=0; i<3; i++) {
console.log(i);
}
// Output:
// 0
// 1
// 2
```
这是通过`for`循环记录数组内容的方法:
```js
const arr = ['a', 'b', 'c'];
for (let i=0; i<3; i++) {
console.log(arr[i]);
}
// Output:
// 'a'
// 'b'
// 'c'
```
如果省略头部的所有三个部分,则会得到无限循环:
```js
for (;;) {
if (Math.random() === 0) break;
}
```
### 21.7。 `for-of`循环
`for-of`循环遍历*可迭代对象* - 一个支持[迭代协议](ch_sync-iteration.html)的数据容器。每个迭代值都存储在一个变量中,在头部中指定:
```js
for («iteration_variable» of «iterable») {
«statements»
}
```
迭代变量通常通过变量语句创建:
```js
const iterable = ['hello', 'world'];
for (const elem of iterable) {
console.log(elem);
}
// Output:
// 'hello'
// 'world'
```
但是您也可以使用已存在的(可变)变量:
```js
const iterable = ['hello', 'world'];
let elem;
for (elem of iterable) {
console.log(elem);
}
```
#### 21.7.1。 `const`:`for-of`与`for`
请注意,在`for-of`循环中,您可以使用`const`。迭代变量对于每次迭代仍然可以是不同的(它在迭代期间不能改变)。将其视为每个迭代的新`const`语句,在新的作用域内。
相反,在`for`循环中,如果它们的值发生变化,则必须通过`let`或`var`语句变量。
#### 21.7.2。可迭代对象的迭代
如前所述,`for-of`适用于任何可迭代对象,而不仅仅是数组。例如,使用集合:
```js
const set = new Set(['hello', 'world']);
for (const elem of set) {
console.log(elem);
}
```
#### 21.7.3。迭代`[index, element]`对数组
最后,您还可以使用`for-of`迭代数组的`[index, element]`条目:
```js
const arr = ['a', 'b', 'c'];
for (const [index, elem] of arr.entries()) {
console.log(`${index} -> ${elem}`);
}
// Output:
// '0 -> a'
// '1 -> b'
// '2 -> c'
```
![](https://img.kancloud.cn/3e/d5/3ed5755d562179ae6c199264f5e21157.svg) **练习:`for-of`**
`exercises/control-flow/array_to_string_test.js`
### 21.8。 `for-await-of`循环
`for-await-of`与`for-of`类似,但它适用于异步迭代而不是同步迭代。它只能在异步函数和异步生成器中使用。
```js
for await (const item of asyncIterable) {
// ···
}
```
`for-await-of`将在后面的章节中详细描述。
### 21.9。 `for-in`循环(避免)
`for-in`有几个陷阱。因此,通常最好避免它。
这是使用`for-in`的一个例子:
```js
function getOwnPropertyNames(obj) {
const result = [];
for (const key in obj) {
if ({}.hasOwnProperty.call(obj, key)) {
result.push(key);
}
}
return result;
}
assert.deepEqual(
getOwnPropertyNames({ a: 1, b:2 }),
['a', 'b']);
assert.deepEqual(
getOwnPropertyNames(['a', 'b']),
['0', '1']); // strings!
```
这是一个更好的选择:
```js
function getOwnPropertyNames(obj) {
const result = [];
for (const key of Object.keys(obj)) {
result.push(key);
}
return result;
}
```
有关`for-in`的更多信息,请参阅[“Speaking JavaScript”](http://speakingjs.com/es5/ch13.html#for-in)。
![](https://img.kancloud.cn/ff/a8/ffa8e16628cad59b09c786b836722faa.svg) **测验**
参见[测验应用程序](ch_quizzes-exercises.html#quizzes)。
- I.背景
- 1.关于本书(ES2019 版)
- 2.常见问题:本书
- 3. JavaScript 的历史和演变
- 4.常见问题:JavaScript
- II.第一步
- 5.概览
- 6.语法
- 7.在控制台上打印信息(console.*)
- 8.断言 API
- 9.测验和练习入门
- III.变量和值
- 10.变量和赋值
- 11.值
- 12.运算符
- IV.原始值
- 13.非值undefined和null
- 14.布尔值
- 15.数字
- 16. Math
- 17. Unicode - 简要介绍(高级)
- 18.字符串
- 19.使用模板字面值和标记模板
- 20.符号
- V.控制流和数据流
- 21.控制流语句
- 22.异常处理
- 23.可调用值
- VI.模块化
- 24.模块
- 25.单个对象
- 26.原型链和类
- 七.集合
- 27.同步迭代
- 28.数组(Array)
- 29.类型化数组:处理二进制数据(高级)
- 30.映射(Map)
- 31. WeakMaps(WeakMap)
- 32.集(Set)
- 33. WeakSets(WeakSet)
- 34.解构
- 35.同步生成器(高级)
- 八.异步
- 36. JavaScript 中的异步编程
- 37.异步编程的 Promise
- 38.异步函数
- IX.更多标准库
- 39.正则表达式(RegExp)
- 40.日期(Date)
- 41.创建和解析 JSON(JSON)
- 42.其余章节在哪里?