原文: http://exploringjs.com/impatient-js/ch_control-flow.html
本章介绍以下控制流语句:
if
语句(ES1)switch
语句(ES3)while
循环(ES1)do-while
循环(ES3)for
循环(ES1)for-of
循环(ES6)for-await-of
循环(ES2018)for-in
循环(ES1)在我们得到实际的控制流语句之前,让我们看看两个用于控制循环的运算符。
break
和continue
当您在其中时,两个运算符break
和continue
可用于控制循环和其他语句。
break
break
有两个版本:一个带有操作数,另一个没有操作数。后一版本在以下语句中起作用:while
,do-while
,for
,for-of
,for-await-of
,for-in
和switch
。它立即离开了当前的声明:
for (const x of ['a', 'b', 'c']) {
console.log(x);
if (x === 'b') break;
console.log('---')
}
// Output:
// 'a'
// '---'
// 'b'
break
的附加用例:留下块带有操作数的break
随处可见。其操作数是 _ 标签 _。标签可以放在任何声明之前,包括块。 break foo
留下标签为foo
的语句:
foo: { // label
if (condition) break foo; // labeled break
// ···
}
如果您使用循环并希望区分找到您要查找的内容并完成循环而没有成功,则从块中断会偶尔会很方便:
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)' }
);
continue
continue
仅适用于while
,do-while
,for
,for-of
,for-await-of
和for-in
。它立即离开当前循环迭代并继续下一个循环。例如:
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'
if
语句这是两个简单的if
语句:一个只有一个“then”分支,一个带有“then”分支和一个“else”分支:
if (cond) {
// then branch
}
if (cond) {
// then branch
} else {
// else branch
}
而不是块,else
也可以跟随另一个if
语句:
if (cond1) {
// ···
} else if (cond2) {
// ···
}
if (cond1) {
// ···
} else if (cond2) {
// ···
} else {
// ···
}
您可以使用更多else if
来继续此链。
if
语句的语法if
语句的一般语法是:
if (cond) «then_statement»
else «else_statement»
到目前为止,then_statement
一直是一个块,但你也可以使用一个声明。该声明必须以分号结束:
if (true) console.log('Yes'); else console.log('No');
这意味着else if
不是它自己的构造,它只是一个if
语句,其else_statement
是另一个if
语句。
switch
声明switch
语句的头部如下所示:
switch («switch_expression») {
«switch_body»
}
在switch
的主体内部,有零个或多个 case 子句:
case «case_expression»:
«statements»
并且,可选地,默认子句:
default:
«statements»
switch
执行如下:
让我们看一个例子:以下函数将一个数字从 1-7 转换为工作日的名称。
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');
return
或break
!在 case 子句的末尾,继续执行下一个 case 子句(除非你return
或break
)。例如:
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
修复此实现:
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');
可以省略 case 子句的语句,这有效地为每个 case 子句提供了多个 case 表达式:
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);
default
子句检查非法值如果switch
表达式没有其他匹配,则跳转到default
子句。这使它对错误检查很有用:
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'});
exercises/control-flow/number_to_month_test.js
奖金:exercises/control-flow/is_object_via_switch_test.js
while
循环while
循环具有以下语法:
while («condition») {
«statements»
}
在每次循环迭代之前,while
评估condition
:
while
主体再次执行。以下代码使用while
循环。在每次循环迭代中,它通过.shift()
删除arr
的第一个元素并记录它。
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
是无限循环:
while (true) {
if (Math.random() === 0) break;
}
do-while
循环do-while
循环的工作原理与while
非常相似,但它会在每次循环迭代后(之前)检查其条件 。
let input;
do {
input = prompt('Enter text:');
} while (input !== ':q');
for
循环使用for
循环,您可以使用头控制其主体的执行方式。头部有三个部分,每个部分都是可选的:
for («initialization»; «condition»; «post_iteration») {
«statements»
}
initialization
:为循环设置变量等。此处通过let
或const
声明的变量仅存在于循环内。condition
:在每次循环迭代之前检查此条件。如果是假的,循环就会停止。post_iteration
:此代码在每次循环迭代后执行。因此,for
循环大致等同于以下while
循环:
«initialization»
while («condition») {
«statements»
«post_iteration»
}
例如,这是如何通过for
循环从零计数到两个:
for (let i=0; i<3; i++) {
console.log(i);
}
// Output:
// 0
// 1
// 2
这是通过for
循环记录数组内容的方法:
const arr = ['a', 'b', 'c'];
for (let i=0; i<3; i++) {
console.log(arr[i]);
}
// Output:
// 'a'
// 'b'
// 'c'
如果省略头部的所有三个部分,则会得到无限循环:
for (;;) {
if (Math.random() === 0) break;
}
for-of
循环for-of
循环遍历 _ 可迭代 _ - 一个支持迭代协议的数据容器。每个迭代值都存储在一个变量中,如 head 中所指定:
for («iteration_variable» of «iterable») {
«statements»
}
迭代变量通常通过变量声明创建:
const iterable = ['hello', 'world'];
for (const elem of iterable) {
console.log(elem);
}
// Output:
// 'hello'
// 'world'
但是您也可以使用已存在的(可变)变量:
const iterable = ['hello', 'world'];
let elem;
for (elem of iterable) {
console.log(elem);
}
const
:for-of
与for
请注意,在for-of
循环中,您可以使用const
。迭代变量对于每次迭代仍然可以是不同的(它在迭代期间不能改变)。将其视为每次执行的新const
声明,在新的范围内。
相反,在for
循环中,如果它们的值发生变化,则必须通过let
或var
声明变量。
如前所述,for-of
适用于任何可迭代对象,而不仅仅是 Arrays。例如,使用集合:
const set = new Set(['hello', 'world']);
for (const elem of set) {
console.log(elem);
}
最后,您还可以使用for-of
迭代 Arrays 的[index,element]条目:
const arr = ['a', 'b', 'c'];
for (const [index, elem] of arr.entries()) {
console.log(`${index} -> ${elem}`);
}
// Output:
// '0 -> a'
// '1 -> b'
// '2 -> c'
exercises/control-flow/array_to_string_test.js
for-await-of
循环for-await-of
与for-of
类似,但它适用于异步迭代而不是同步迭代。它只能在异步函数和异步生成器中使用。
for await (const item of asyncIterable) {
// ···
}
for-await-of
将在后面的章节中详细描述。
for-in
循环(避免)for-in
有几个陷阱。因此,通常最好避免它。
这是使用for-in
的一个例子:
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!
这是一个更好的选择:
function getOwnPropertyNames(obj) {
const result = [];
for (const key of Object.keys(obj)) {
result.push(key);
}
return result;
}
有关for-in
的更多信息,请参阅“Speaking JavaScript”。
参见测验应用程序。