21.控制流语句

原文: 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)

在我们得到实际的控制流语句之前,让我们看看两个用于控制循环的运算符。

21.1。控制循环:breakcontinue

当您在其中时,两个运算符breakcontinue可用于控制循环和其他语句。

21.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'

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

21.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'

21.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来继续此链。

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

21.3。 switch声明

switch语句的头部如下所示:

switch («switch_expression») {
  «switch_body»
}

switch的主体内部,有零个或多个 case 子句:

case «case_expression»:
  «statements»

并且,可选地,默认子句:

default:
  «statements»

switch执行如下:

  • 评估切换表达式。
  • 跳转到第一个 case 子句,其表达式与 switch 表达式具有相同的结果。
  • 如果没有这样的 case 子句,请跳转到 default 子句。
  • 如果没有默认子句,则不会发生任何事情。

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

21.3.2。别忘了returnbreak

在 case 子句的末尾,继续执行下一个 case 子句(除非你returnbreak)。例如:

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

21.3.3。空案件条款

可以省略 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);

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

练习:switch

  • exercises/control-flow/number_to_month_test.js

  • 奖金:exercises/control-flow/is_object_via_switch_test.js

21.4。 while循环

while循环具有以下语法:

while («condition») {
  «statements»
}

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

  • 如果结果是假的,则循环结束。
  • 如果结果是真实的,则while主体再次执行。

21.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;
}

21.5。 do-while循环

do-while循环的工作原理与while非常相似,但它会在每次循环迭代后(之前)检查其条件

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

21.6。 for循环

使用for循环,您可以使用头控制其主体的执行方式。头部有三个部分,每个部分都是可选的:

for («initialization»; «condition»; «post_iteration») {
  «statements»
}
  • initialization:为循环设置变量等。此处通过letconst声明的变量仅存在于循环内。
  • condition:在每次循环迭代之前检查此条件。如果是假的,循环就会停止。
  • post_iteration:此代码在每次循环迭代后执行。

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

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

21.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;
}

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

21.7.1。 constfor-offor

请注意,在for-of循环中,您可以使用const。迭代变量对于每次迭代仍然可以是不同的(它在迭代期间不能改变)。将其视为每次执行的新const声明,在新的范围内。

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

21.7.2。迭代迭代

如前所述,for-of适用于任何可迭代对象,而不仅仅是 Arrays。例如,使用集合:

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

21.7.3。迭代[index,element]对数组

最后,您还可以使用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'

练习:for-of

exercises/control-flow/array_to_string_test.js

21.8。 for-await-of循环

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

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

for-await-of将在后面的章节中详细描述。

21.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”

测验

参见测验应用程序


书籍推荐