JavaScript for impatient programmers (ES2022 edition)
请支持本书:购买捐赠
(广告,请勿屏蔽。)

23 控制流语句



本章涵盖以下控制流语句

23.1 控制循环:breakcontinue

breakcontinue 这两个运算符可用于在我们位于循环和其他语句内部时控制它们。

23.1.1 break

break 有两种版本:一种带操作数,一种不带操作数。后者版本适用于以下语句:whiledo-whileforfor-offor-await-offor-inswitch。它立即退出当前语句

for (const x of ['a', 'b', 'c']) {
  console.log(x);
  if (x === 'b') break;
  console.log('---')
}

// Output:
// 'a'
// '---'
// 'b'

23.1.2 break 加标签:退出任何带标签的语句

带操作数的 break 可以在任何地方使用。其操作数是一个*标签*。标签可以放在任何语句的前面,包括块。break my_label 退出标签为 my_label 的语句

my_label: { // label
  if (condition) break my_label; // labeled break
  // ···
}

在以下示例中,搜索可以

function findSuffix(stringArray, suffix) {
  let result;
  search_block: {
    for (const str of stringArray) {
      if (str.endsWith(suffix)) {
        // Success:
        result = str;
        break search_block; // (A)
      }
    } // for
    // Failure:
    result = '(Untitled)'; // (B)
  } // search_block

  return { suffix, result };
    // Same as: {suffix: suffix, result: result}
}
assert.deepEqual(
  findSuffix(['notes.txt', 'index.html'], '.html'),
  { suffix: '.html', result: 'index.html' }
);
assert.deepEqual(
  findSuffix(['notes.txt', 'index.html'], '.mjs'),
  { suffix: '.mjs', result: '(Untitled)' }
);

23.1.3 continue

continue 仅适用于 whiledo-whileforfor-offor-await-offor-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'

23.2 控制流语句的条件

ifwhiledo-while 具有原则上为布尔值的条件。但是,条件只需为*真值*(如果强制转换为布尔值为 true)即可被接受。换句话说,以下两个控制流语句是等效的

if (value) {}
if (Boolean(value) === true) {}

这是所有*假值*的列表

所有其他值都是真值。有关更多信息,请参阅 §15.2 “假值和真值”

23.3 if 语句 [ES1]

这是两个简单的 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 继续此链。

23.3.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 语句。

23.4 switch 语句 [ES3]

switch 语句如下所示

switch («switch_expression») {
  «switch_body»
}

switch 的主体由零个或多个 case 子句组成

case «case_expression»:
  «statements»

以及可选的 default 子句

default:
  «statements»

switch 的执行方式如下

23.4.1 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');

23.4.2 不要忘记 returnbreak

在 case 子句的末尾,执行将继续执行下一条 case 子句,除非我们使用 returnbreak – 例如

function englishToFrench(english) {
  let french;
  switch (english) {
    case 'hello':
      french = 'bonjour';
    case 'goodbye':
      french = 'au revoir';
  }
  return french;
}
// The result should be 'bonjour'!
assert.equal(englishToFrench('hello'), 'au revoir');

也就是说,我们对 dayOfTheWeek() 的实现之所以有效,是因为我们使用了 return。我们可以通过使用 break 来修复 englishToFrench()

function englishToFrench(english) {
  let french;
  switch (english) {
    case 'hello':
      french = 'bonjour';
      break;
    case 'goodbye':
      french = 'au revoir';
      break;
  }
  return french;
}
assert.equal(englishToFrench('hello'), 'bonjour'); // ok

23.4.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);

23.4.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'});

  练习:switch

23.5 while 循环 [ES1]

while 循环具有以下语法

while («condition») {
  «statements»
}

在每次循环迭代之前,while 都会评估 condition

23.5.1 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;
}

23.6 do-while 循环 [ES3]

do-while 循环的工作方式与 while 非常相似,但它在每次循环迭代*之后*检查其条件,而不是之前。

let input;
do {
  input = prompt('Enter text:');
  console.log(input);
} while (input !== ':q');

do-while 也可以看作是至少运行一次的 while 循环。

prompt() 是一个在 Web 浏览器中可用的全局函数。它提示用户输入文本并返回它。

23.7 for 循环 [ES1]

for 循环具有以下语法

for («initialization»; «condition»; «post_iteration») {
  «statements»
}

第一行是循环的*头部*,它控制*主体*(循环的其余部分)执行的次数。它包含三个部分,每个部分都是可选的

因此,for 循环大致等效于以下 while 循环

«initialization»
while («condition») {
  «statements»
  «post_iteration»
}

23.7.1 for 循环的示例

例如,以下是通过 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<arr.length; i++) {
  console.log(arr[i]);
}

// Output:
// 'a'
// 'b'
// 'c'

如果我们省略头部的所有三个部分,我们将得到一个无限循环

for (;;) {
  if (Math.random() === 0) break;
}

23.8 for-of 循环 [ES6]

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);
}

23.8.1 constfor-offor

请注意,在 for-of 循环中,我们可以使用 const。迭代变量对于每次迭代仍然可以不同(它只是在迭代过程中不能更改)。将其视为每次都在新的作用域中执行新的 const 声明。

相反,在 for 循环中,如果变量的值发生变化,我们必须通过 letvar 声明变量。

23.8.2 迭代可迭代对象

如前所述,for-of 适用于任何可迭代对象,而不仅仅是数组 – 例如,集

const set = new Set(['hello', 'world']);
for (const elem of set) {
  console.log(elem);
}

23.8.3 迭代数组的 [索引,元素] 对

最后,我们还可以使用 for-of 迭代数组的 [索引,元素] 条目

const arr = ['a', 'b', 'c'];
for (const [index, elem] of arr.entries()) {
  console.log(`${index} -> ${elem}`);
}
// Output:
// '0 -> a'
// '1 -> b'
// '2 -> c'

使用 [index, element],我们正在使用 *解构* 来访问数组元素。

  练习:for-of

exercises/control-flow/array_to_string_test.mjs

23.9 for-await-of 循环 [ES2018]

for-await-of 类似于 for-of,但它适用于异步可迭代对象,而不是同步可迭代对象。并且它只能在异步函数和异步生成器中使用。

for await (const item of asyncIterable) {
  // ···
}

异步迭代章节 中详细描述了 for-await-of

23.10 for-in 循环(避免使用) [ES1]

for-in 循环访问对象的 all(自身和继承的)可枚举属性键。当循环遍历数组时,它很少是一个好的选择

以下代码演示了这些要点

const arr = ['a', 'b', 'c'];
arr.propKey = 'property value';

for (const key in arr) {
  console.log(key);
}

// Output:
// '0'
// '1'
// '2'
// 'propKey'

23.11 循环建议

  测验

请参阅 测验应用程序