关于 JavaScript 循环

Avatar of Erick Merchant
Erick Merchant

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

每种编程语言都有循环。循环执行一个操作(即一段工作)多次,通常是针对数组或列表中的每个项目执行一次,或者只是重复执行一个操作,直到满足某个条件。

特别是 JavaScript 有很多不同类型的循环。我甚至没有用过所有这些,所以出于好奇,我想对它们做一个高级概述。事实证明,我至少没有使用其中几种不同类型是有充分理由的。

所以,现在让我们花点while时间探索不同类型的循环,我们可以用每个循环do做什么,以及为什么你可能会选择一个而不是另一个。(到最后,你会觉得这个文字游戏非常有趣。)

whiledo...while 循环

首先是 while 循环。它是最基本的循环类型,在许多情况下,它可能是最容易阅读和最快的。它通常用于执行某个操作,直到满足某个条件。它也是创建无限循环或永不停歇循环的最简单方法。还有 do...while 语句。实际上,唯一的区别在于条件是在每次迭代的结尾检查还是在开头检查。

// remove the first item from an array and log it until the array is empty
let queue1 = ["a", "b", "c"];

while (queue1.length) {
  let item = queue1.shift();

  console.log(item);
}

// same as above but also log when the array is empty
let queue2 = [];

do {
  let item = queue2.shift() ?? "empty";

  console.log(item);
} while (queue2.length);

for 循环

接下来是 for 循环。它应该是执行某个操作一定次数的首选方式。如果您需要重复某个操作,比如 10 次,那么请使用 for 循环。这个特定的循环对于编程新手来说可能令人生畏,但是用 while 风格的循环重写相同的循环可以帮助说明语法,并更容易记住。

// log the numbers 1 to 5
for (let i = 1; i <= 5; i++) {
  console.log(i);
}

// same thing but as a while loop
let i = 1; // the first part of a for loop

// the second
while (i <= 5) {
  console.log(i);

  i++; // the third
}

("end");

for...offor await...of 循环

for...of 循环是遍历数组的最简单方法。

let myList = ["a", "b", "c"];

for (let item of myList) {
  console.log(item);
}

但是它们并不局限于数组。从技术上讲,它们可以迭代任何实现了所谓的 可迭代协议 的东西。有一些内置类型实现了该协议:数组、映射、集合和字符串,仅举最常见的例子,但您可以在自己的代码中实现该协议。您需要做的是向任何对象添加 [Symbol.iterator] 方法,并且该方法应该返回一个迭代器。这有点令人困惑,但要点是可迭代对象是具有特殊方法(返回迭代器)的对象;如果您愿意,可以将其视为迭代器的工厂方法。一种称为 生成器 的特殊类型的函数是一个既返回可迭代对象又返回迭代器的函数。

let myList = {
  *[Symbol.iterator]() {
    yield "a";
    yield "b";
    yield "c";
  },
};

for (let item of myList) {
  console.log(item);
}

还有我刚才提到的所有内容的 async 版本:async 可迭代对象、async 迭代器和 async 生成器。您可以将 async 可迭代对象与 for await...of 一起使用。

async function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

// this time we're not making an iterable, but a generator
async function* aNumberAMinute() {
  let i = 0;

  while (true) {
    // an infinite loop
    yield i++;

    // pause a minute
    await delay(60_000);
  }
}

// it's a generator, so we need to call it ourselves
for await (let i of aNumberAMinute()) {
  console.log(i);

  // stop after one hour
  if (i >= 59) {
    break;
  }
}

关于 for await...of 语句的一个不明显的事情是,您可以将其与非异步可迭代对象一起使用,并且它可以正常工作。但是,反过来则不行;您不能将 async 可迭代对象与 for...of 语句一起使用。

forEachmap 循环

虽然从技术上讲,这些本身并不是循环,但您可以使用它们来迭代列表。

以下是关于 forEach 方法的事情。从历史上看,它比使用 for 循环慢得多。我认为在某些情况下,这可能不再是事实,但是如果性能是一个问题,那么我建议避免使用它。现在我们有了 for...of,我不确定还有多少理由使用它。我想它仍然可能出现的唯一原因是,如果您有一个准备用作回调的函数,但是您可以轻松地从 for...of 的主体内部调用该函数。

forEach 还会接收每个项目的索引,所以这可能是您也需要的。最终,使用它的决定可能取决于您正在使用的其他任何代码是否使用了它,但我个人会在编写新代码时避免使用它。

let myList = ["a", "b", "c"];

for (let item of myList) {
	console.log(item);
}

// but maybe if I need the index use forEach
["a", "b", "c"].forEach((item, index) => {
  console.log(`${index}: ${item}`);
});

同时,map 基本上将一个数组转换为另一个数组。它仍然具有与 forEach 相同的性能影响,但它比替代方案更易于阅读。当然,这是主观的,就像 forEach 一样,您需要做与其他代码相同的事情。在 React 和受 React 启发的库中,您会看到它大量用于遍历数组并在 JSX 中输出项目列表。

function MyList({items}) {
  return (
    <ul>
      {items.map((item) => {
        return <li>{item}</li>;
      })}
    </ul>
  );
}

for...in 循环

JavaScript 中的循环列表如果没有提到 for...in 语句就不完整,因为它可以循环遍历对象的字段。不过,它也会访问通过对象原型链继承的字段,出于这个原因,我一直避免使用它。

也就是说,如果您有一个对象字面量,那么 for...in 可能是遍历该对象键的可行方法。还需要注意的是,如果您已经编写了很长时间的 JavaScript 代码,您可能会记得键的顺序在浏览器之间曾经是不一致的,但现在顺序是一致的。任何可以作为数组索引的键(即正整数)都将按升序排列在最前面,然后是其他所有键,其顺序与编写时的顺序相同。

let myObject = {
  a: 1,
  b: 2,
  c: 3,
};

for (let k in myObject) {
  console.log(myObject[k]);
}

总结

循环是许多程序员每天都会使用的东西,尽管我们可能会认为它们理所当然,并且不会过多地考虑它们。

但是,当您退一步看看我们在 JavaScript 中遍历事物的所有方法时,事实证明有几种方法可以做到这一点。不仅如此,它们之间还存在重大(如果不是细微的)差异,这些差异会影响您对脚本的方法。