本章介绍以下控制流语句:
if语句(ES1)
switch语句(ES3)
while循环(ES1)
do-while循环(ES3)
for循环(ES1)
for-of循环(ES6)
for-await-of循环(ES2018)
for-in循环(ES1)
19.1 控制循环:break
和continue
当您在循环中时,两个运算符break
和continue
可用于控制循环和其他语句。
19.1.1 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'
19.1.2 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)' }
);
19.1.3 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'
19.2 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
来继续这个链条。
19.2.1 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
语句。
19.3 switch
语句
switch
语句的头部如下所示:
switch («switch_expression») {
«switch_body»
}
在switch
的正文内部,有零个或多个 case 子句:
case «case_expression»:
«statements»
并且默认子句是可选的:
default:
«statements»
switch
执行如下:
- 求值
switch
表达式。 - 跳转到第一个
case
子句,其表达式与switch
表达式结果相同。 - 如果没有这样的
case
子句,请跳转到default
子句。 - 如果没有默认子句,则不会发生任何事情。
19.3.1 第一个例子
让我们看一个例子:以下函数将一个数字从 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');
19.3.2 别忘了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');
19.3.3 空case
子句
可以省略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);
19.3.4 通过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'});
19.4 while
循环
while
循环具有以下语法:
while («condition») {
«statements»
}
在每次循环之前,while
求值condition
:
- 如果结果是假值,则循环结束。
- 如果结果是真值,则
while
主体再次执行。
19.4.1 例子
以下代码使用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;
}
19.5 do-while
循环
do-while
循环的工作原理与while
非常相似,但它会在每次循环之后(之前)检查其条件。
let input;
do {
input = prompt('Enter text:');
} while (input !== ':q');
19.6 for
循环
对于for
循环,您可以使用头部控制其主体的执行方式。头部有三个部分,每个部分都是可选的:
for («initialization»; «condition»; «post_iteration») {
«statements»
}
initialization
:为循环设置变量等。此处通过let
或const
语句的变量仅存在于循环内。condition
:在每次循环之前检查此条件。如果是假值,循环就会停止。post_iteration
:此代码在每次循环之后执行。
因此,for
循环大致等同于以下while
循环:
«initialization»
while («condition») {
«statements»
«post_iteration»
}
19.6.1 例子
例如,这是如何通过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;
}
19.7 for-of
循环
for-of
循环遍历可迭代对象 - 一个支持迭代协议的数据容器。每个迭代值都存储在一个变量中,在头部中指定:
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);
}
19.7.1 const
:for-of
与for
请注意,在for-of
循环中,您可以使用const
。迭代变量对于每次迭代仍然可以是不同的(它在迭代期间不能改变)。将其视为每个迭代的新const
语句,在新的作用域内。相反,在for
循环中,如果它们的值发生变化,则必须通过let
或var
语句变量。
19.7.2 可迭代对象的迭代
如前所述,for-of
适用于任何可迭代对象,而不仅仅是数组。例如,使用集合:
const set = new Set(['hello', 'world']);
for (const elem of set) {
console.log(elem);
}
19.7.3 迭代[index, element]
对数组
最后,您还可以使用for-of
迭代数组的[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'
19.8 for-await-of
循环
for-await-of
与for-of
类似,但它适用于异步迭代而不是同步迭代。它只能在异步函数和异步生成器中使用。
for await (const item of asyncIterable) {
// ···
}
for-await-of
将在后面的章节中详细描述。
19.9 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”。
下一节:本章介绍 JavaScript 如何处理异常。暂且不说:JavaScript 直到 ES3 才支持异常。这就解释了为什么它们被语言及其标准库谨慎使用。