19.控制流语句

本章介绍以下控制流语句:
if语句(ES1)
switch语句(ES3)
while循环(ES1)
do-while循环(ES3)
for循环(ES1)
for-of循环(ES6)
for-await-of循环(ES2018)
for-in循环(ES1)

19.1 控制循环:breakcontinue

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

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

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仅适用于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'

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 别忘了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');

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:为循环设置变量等。此处通过letconst语句的变量仅存在于循环内。
  • 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 constfor-offor

请注意,在for-of循环中,您可以使用const。迭代变量对于每次迭代仍然可以是不同的(它在迭代期间不能改变)。将其视为每个迭代的新const语句,在新的作用域内。相反,在for循环中,如果它们的值发生变化,则必须通过letvar语句变量。

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-offor-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 才支持异常。这就解释了为什么它们被语言及其标准库谨慎使用。