在 JavaScript 中的数组上的 For-each

IT技术 javascript arrays loops foreach iteration
2020-12-28 22:56:12

如何使用 JavaScript 遍历数组中的所有条目?

我以为是这样的:

forEach(instance in theArray)

theArray我的数组在哪里,但这似乎不正确。

6个回答

TL; 博士

  • 你最好的选择通常是

    • 一个for-of循环(仅限 ES2015+;规范| MDN)——简单且async友好
      for (const element of theArray) {
          // ...use `element`...
      }
      
    • forEach(ES5 +只;规格| MDN)(或其亲属some和这样) - async-友好(而见详情)
      theArray.forEach(element => {
          // ...use `element`...
      });
      
    • 一个简单的老式for循环——async友好
      for (let index = 0; index < theArray.length; ++index) {
          const element = theArray[index];
          // ...use `element`...
      }
      
    • (很少) for-in 有保护措施- -async友好
      for (const propertyName in theArray) {
          if (/*...is an array element property (see below)...*/) {
              const element = theArray[propertyName];
              // ...use `element`...
          }
      }
      
  • 一些快速的“不要”:

    • 不要使用,for-in除非您使用它有安全措施,或者至少知道它为什么会咬你。
    • 不要使用map,如果你不使用它的返回值
      (可悲的是有人在那里教map[ spec / MDN ] 好像是这样forEach ——但正如我在我的博客上写的那样,这不是它的用途。如果你不使用它创建的数组,请不要使用map。)
    • 不要使用forEach,如果回调不异步工作,并且希望forEach等到这项工作完成(因为它不会)。

但是还有很多东西要探索,请继续阅读...


JavaScript 具有强大的语义来循环遍历数组和类数组对象。我将答案分为两部分:真正数组的选项,以及类似数组的选项,例如arguments对象、其他可迭代对象 (ES2015+)、DOM 集合等。

好的,让我们看看我们的选择:

对于实际数组

您有五个选项(两个基本上永久支持,另一个由 ECMAScript 5 [“ES5”] 添加,另外两个在 ECMAScript 2015(“ES2015”,又名“ES6”)中添加:

  1. 使用for-of(隐式使用迭代器)(ES2015+)
  2. 使用forEach及相关(ES5+)
  3. 使用简单的for循环
  4. 正确使用for-in
  5. 显式使用迭代器(ES2015+)

(你可以在这里看到那些旧的规范:ES5ES2015,但两者都被取代了;当前编辑的草稿总是在这里。)

细节:

1.使用for-of(隐式使用迭代器)(ES2015+)

ES2015向 JavaScript添加了迭代器和可迭代对象数组是可迭代的(字符串、Maps 和Sets 以及 DOM 集合和列表也是如此,稍后您将看到)。可迭代对象为其值提供迭代器。newfor-of语句循环遍历迭代器返回的值:

const a = ["a", "b", "c"];
for (const element of a) { // You can use `let` instead of `const` if you like
    console.log(element);
}
// a
// b
// c

没有比这更简单的了!在幕后,它从数组中获取一个迭代器并遍历迭代器返回的值。数组提供的迭代器提供数组元素的值,从开始到结束。

注意element每个循环迭代的范围如何尝试element在循环结束后使用会失败,因为它不存在于循环体之外。

理论上,一个for-of循环涉及多个函数调用(一个用于获取迭代器,然后一个用于从中获取每个值)。即使这是真的,也没有什么可担心的,现代 JavaScript 引擎中的函数调用非常便宜(它困扰着我forEach[below] 直到我研究它;细节)。但此外,在处理数组等本机迭代器时,JavaScript 引擎会优化这些调用(在性能关键代码中)。

for-of是完全async友好的。如果您需要在循环体中串联(而不是并行)完成工作,循环体中的一个awaitin 循环体将在继续之前等待Promise解决。这是一个愚蠢的例子:

请注意这些词是如何在每个词之前延迟出现的。

这是编码风格的问题,但这for-of是我在遍历任何可迭代的东西时首先要接触的东西。

2.用途forEach及相关

在任何可以访问ArrayES5 添加功能的模糊现代环境(因此,不是 IE8)中,如果您只处理同步代码(或者您不需要等待),则可以使用forEach( spec | MDN )用于在循环期间完成的异步进程):

const a = ["a", "b", "c"];
a.forEach((element) => {
    console.log(element);
});

forEach接受一个回调函数和一个可选的值,作为this调用该回调时使用的值(上面未使用)。为数组中的每个元素调用回调,按顺序跳过稀疏数组中不存在的元素。虽然我只使用了上面的一个参数,但回调函数是用三个参数调用的:该迭代的元素、该元素的索引以及对您正在迭代的数组的引用(以防您的函数还没有它便利)。

for-of,forEach的优点是您不必在包含范围内声明索引和值变量;在这种情况下,它们作为迭代函数的参数提供,并且很好地限定在该迭代中。

for-of,forEach的缺点是它不理解async函数和await. 如果您使用async函数作为回调,forEach在继续之前等待该函数的Promise解决。这是使用替代async示例- 请注意初始延迟是如何出现的,但随后所有文本都会立即出现而不是等待:for-offorEach

forEach 是“循环遍历所有”函数,但 ES5 定义了其他几个有用的“遍历数组并执行操作”函数,包括:

  • every( spec | MDN ) - 回调第一次返回假值时停止循环
  • some( spec | MDN ) - 回调第一次返回真值时停止循环
  • filter( spec | MDN ) - 创建一个新数组,其中包含回调返回真值的元素,省略不返回真值的元素
  • map( spec | MDN ) - 从回调返回的值创建一个新数组
  • reduce( spec | MDN ) - 通过重复调用回调来建立一个值,传入以前的值;有关详细信息,请参阅规范
  • reduceRight( spec | MDN ) - 类似reduce,但按降序而不是升序工作

和 一样forEach,如果你使用一个async函数作为你的回调函数,那么这些函数都不会等待函数的Promise完成。这意味着:

  • 使用一个async回调函数从来没有通过适当的everysome以及filter因为它们将把返回的Promise,就好像是一个truthy值; 他们不会等待Promise解决,然后使用履行value。
  • 使用async函数回调通常适用于map如果目标是将某物的数组转换为promise的数组,可能是为了传递给其中一个 promise 组合器函数(Promise.allPromise.racepromise.allSettled、 或Promise.any)。
  • 使用async函数回调很少适合与reduceor reduceRight,因为(再次)回调将始终返回一个Promise。但是有一种习惯用法是从使用reduce( const promise = array.reduce((p, element) => p.then(/*...something using `element`...*/));)的数组构建Promise链,但通常在这些情况下函数中for-oforfor循环async会更清晰且更易于调试。

3. 使用简单的for循环

有时旧的方法是最好的:

const a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
    const element = a[index];
    console.log(element);
}

如果数组的长度将不会在循环过程中改变,它在高的性能敏感的代码,一个稍微复杂一点的版本抓住了长度达阵可能是一个很小的有点快:

const a = ["a", "b", "c"];
for (let index = 0, len = a.length; index < len; ++index) {
    const element = a[index];
    console.log(element);
}

和/或倒数:

const a = ["a", "b", "c"];
for (let index = a.length - 1; index >= 0; --index) {
    const element = a[index];
    console.log(element);
}

但是对于现代 JavaScript 引擎,您很少需要勉强挤出最后一点力气。

在 ES2015 之前,循环变量必须存在于包含作用域中,因为var只有函数级作用域,没有块级作用域。但是正如您在上面的示例中看到的那样,您可以let在 内使用for将变量范围限定为循环。当你这样做时,index每次循环迭代都会重新创建变量,这意味着在循环体中创建的闭包会保留index对特定迭代的引用,这解决了旧的“循环中的闭包”问题:

在上面,如果你点击第一个,你会得到“Index is: 0”,如果你点击最后一个,你会得到“Index is: 4”。这并没有,如果你使用的工作var,而不是let(你永远看“:5指数”)。

就像for-offor循环在async函数运行良好。这是使用for循环的较早示例

4、正确使用for-in

for-in不是用于遍历数组,而是用于遍历对象属性的名称。作为数组是对象这一事实的副产品,它似乎经常用于循环遍历数组,但它不仅循环遍历数组索引,还循环遍历对象的所有可枚举属性(包括继承的属性)。(也曾经是没有指定顺序;现在是[详情在这个其他答案中],但是即使现在指定了顺序,规则也很复杂,也有例外,依赖顺序不是最佳实践。)

for-in数组上唯一真正的用例是:

  • 它是一个稀疏数组,其中有大量间隙,或者
  • 您在数组对象上使用非元素属性,并且希望将它们包含在循环中

仅查看第一个示例:for-in如果您使用适当的保护措施,您可以使用访问那些稀疏数组元素:

// `a` is a sparse array
const a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (const name in a) {
    if (Object.hasOwn(a, name) &&       // These checks are
        /^0$|^[1-9]\d*$/.test(name) &&  // explained
        name <= 4294967294              // below
       ) {
        const element = a[name];
        console.log(a[name]);
    }
}

注意三项检查:

  1. 该对象具有该名称的自己的属性(不是它从其原型继承属性;此检查也经常被编写为a.hasOwnProperty(name)但 ES2022 添加了Object.hasOwn它可以更可靠),以及

  2. 名称都是十进制数字(例如,正常的字符串形式,而不是科学记数法),以及

  3. 强制为数字时名称的值为 <= 2^32 - 2(即 4,294,967,294)。这个数字从何而来?它是规范中数组索引定义的一部分其他数字(非整数、负数、大于 2^32 - 2 的数字)不是数组索引。它是 2^32 - 2的原因是这使得最大索引值低于 2^32 - 1,这是数组length可以具有的最大值(例如,数组的长度适合 32 位无符号整数。)

...尽管如此,大多数代码只进行hasOwnProperty检查。

当然,您不会在内联代码中这样做。你会写一个实用程序函数。也许:

for,for-in如果其中的工作需要串行完成,则在异步函数中效果很好。

5. 显式使用迭代器(ES2015+)

for-of隐式使用迭代器,为您完成所有 scut 工作。有时,您可能希望显式使用迭代器它看起来像这样:

const a = ["a", "b", "c"];
const it = a.values(); // Or `const it = a[Symbol.iterator]();` if you like
let entry;
while (!(entry = it.next()).done) {
    const element = entry.value;
    console.log(element);
}

迭代器是与规范中的迭代器定义相匹配的对象。每次调用它的next方法时,它都会返回一个新的结果对象结果对象有一个属性 ,done告诉我们它是否完成,还有一个属性value包含该迭代的值。done如果是falsevalue则是可选的,如果则是可选的undefined。)

你得到的东西value取决于迭代器。阵列上,缺省迭代器提供每个阵列元素的值("a""b",和"c"在前面的例子)。数组还有其他三个返回迭代器的方法:

  • values():这是[Symbol.iterator]返回默认迭代器方法的别名
  • keys(): 返回一个迭代器,它提供数组中的每个键(索引)。在上面的示例中,它将提供"0", then "1", then "2"(是的,作为字符串)。
  • entries(): 返回一个提供[key, value]数组的迭代器

由于迭代器对象在您调用 之前不会前进next,因此它们在async函数循环运行良好这是先前for-of明确使用迭代器的示例:

对于类数组对象

除了真正的数组,还有类似数组的对象,它们有一个length属性和全数字名称的属性:NodeListinstancesHTMLCollectioninstancesargumentsobject 等。我们如何遍历它们的内容?

使用上面的大部分选项

上面的数组方法中至少有一些,可能是大部分甚至全部,同样适用于类似数组的对象:

  1. 使用for-of(隐式使用迭代器)(ES2015+)

    for-of使用对象提供迭代器(如果有)。这包括主机提供的对象(如 DOM 集合和列表)。例如,HTMLCollection来自getElementsByXYZ方法NodeList的实例来自querySelectorAll两者的s 实例都支持迭代。(这是由 HTML 和 DOM 规范非常巧妙地定义的。基本上,任何具有length和 索引访问的对象都是自动可迭代的。它不必被标记iterable;这仅用于集合,除了可迭代之外,还支持forEachvalues, keys, 和entriesmethods. NodeListdo; HTMLCollectionnot, 但两者都是可迭代的。)

    下面是一个循环遍历div元素的例子

  1. 使用forEach及相关(ES5+)

    on 的各种函数Array.prototype是“有意通用的”,可以通过Function#call( spec | MDN ) 或Function#apply( spec | MDN )用于类数组对象(如果您必须处理 IE8 或更早版本 [ouch],请参阅本答案末尾的“主机提供对象的警告”,但这对于模糊现代的浏览器来说不是问题。)

    假设您想forEach在 aNodechildNodes集合上使用(作为 ,本机HTMLCollection没有forEach)。你会这样做:

    Array.prototype.forEach.call(node.childNodes, (child) => {
        // Do something with `child`
    });
    

    (但请注意,您可以只使用for-ofon node.childNodes。)

    如果您打算经常这样做,您可能希望将函数引用的副本抓取到变量中以供重用,例如:

    // (This is all presumably in a module or some scoping function)
    const forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
    
    // Then later...
    forEach(node.childNodes, (child) => {
        // Do something with `child`
    });
    
  2. 使用简单的for循环

    也许很明显,一个简单的for循环适用于类似数组的对象。

  3. 显式使用迭代器(ES2015+)

    见#1。

可能能够逃脱for-in(使用安全措施),但是有了所有这些更合适的选项,就没有理由尝试了。

创建一个真正的数组

其他时候,您可能希望将类似数组的对象转换为真正的数组。做到这一点非常简单:

  1. Array.from

    Array.from (规格) | (MDN)(ES2015+,但很容易填充)从一个类似数组的对象创建一个数组,可以选择首先通过映射函数传递条目。所以:

    const divs = Array.from(document.querySelectorAll("div"));
    

    ...获取NodeListfromquerySelectorAll从中创建一个数组。

    如果您打算以某种方式映射内容,映射功能会很方便。例如,如果您想获取具有给定类的元素的标签名称数组:

    // Typical use (with an arrow function):
    const divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
    
    // Traditional function (since `Array.from` can be polyfilled):
    var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
        return element.tagName;
    });
    
  2. 使用扩展语法 ( ...)

    也可以使用 ES2015 的扩展语法就像for-of,这使用了对象提供迭代器(参见上一节中的 #1):

    const trueArray = [...iterableObject];
    

    因此,例如,如果我们想将 a 转换NodeList为一个真正的数组,使用展开语法这会变得非常简洁:

    const divs = [...document.querySelectorAll("div")];
    
  3. 使用slice数组方法

    我们可以使用slice数组方法,与上面提到的其他方法一样,它是“有意通用的”,因此可以与类似数组的对象一起使用,如下所示:

    const trueArray = Array.prototype.slice.call(arrayLikeObject);
    

    因此,例如,如果我们想将 aNodeList转换为真正的数组,我们可以这样做:

    const divs = Array.prototype.slice.call(document.querySelectorAll("div"));
    

    (如果您仍然必须处理 IE8 [哎哟],将会失败;IE8 不允许您像this那样使用主机提供的对象。)

主机提供的对象的警告

如果您使用主机提供的类似数组的对象的Array.prototype函数(例如,DOM 集合等由浏览器而不是 JavaScript 引擎提供),IE8 等过时的浏览器不一定会处理这种情况,因此如果您必须支持它们,请务必在您的目标环境中进行测试。但这对于模糊现代的浏览器来说不是问题。(对于非浏览器环境,自然会因环境而异。)

@Alex - 不代表数组元素的数组属性。例如:const a = ["a", "b"]; a.example = 42;该数组有三个属性(比那些所有阵列具有其他),其名称是字符串"0""1""example"命名的属性"example"是非元素属性。另外两个是元素属性,因为它们代表数组的元素。
2021-02-07 22:56:12
@PeterKionga-Kamau - 问题和答案是关于数组,而不是(其他)对象。但是:对象属性没有索引,所以对象没有索引访问;相反,他们键控访问(theObject.propNametheObject["propName"]theObject[propKeySymbol]等)。索引访问的概念对于对象没有用处。不过,这是非常间接的可能。🙂 对象属性现在确实有顺序(ES2015+,在以后的几个规范中进行了调整),但是顺序很复杂,它取决于属性创建的顺序,属性键的类型,......
2021-02-15 22:56:12
...属性键的值(!),如果它是一个字符串,以及该属性是继承的还是“拥有的”,所以依赖属性顺序是不好的做法。如果无论如何都想这样做,没有一个操作可以按顺序提供所有属性键,而是按顺序Reflect.ownKeys提供对象自己的属性键的数组(跳过继承的)。因此,如果这适合用例,您可以从 ( const keys = Reflect.ownKeys(theObject);) 中获取一个数组...
2021-03-06 22:56:12
@PeterKionga-Kamau - 这不是关联数组,而是一个对象。您正在创建的数组var arr = new Array();被丢弃并替换为您正在创建的对象arr = {"test":"testval", "test2":"test2val"};该代码应该只是var arr = {"test":"testval", "test2":"test2val"};(好吧,不是var,但是letconst)。当然,根据某些定义,对象可能被认为是关联数组,但在其他定义中,它们不是,我在 JS 中避免使用该术语,因为它在 PHP 中具有特定含义,与 JavaScript 相邻,因为它们都在 Web 工作中被大量使用.
2021-03-08 22:56:12
......那么,“索引”的访问将是索引到该数组为重点,然后让使用该键对象的值:theObject[keys[index]]不过,我无法想象它的用例。如果你想要对象的所有属性(继承的 + 自己的),那就更复杂了,你必须遍历原型链附加你还没有看到的属性(Set在那里可能很有用,因为Set它严格按值插入排序并且不'不允许重复):...
2021-03-08 22:56:12

注意:这个答案已经过时了。对于更现代的方法,请查看array 上可用的方法感兴趣的方法可能是:

  • 为每个
  • 地图
  • 筛选
  • 压缩
  • 降低
  • 每一个
  • 一些

JavaScript 中迭代数组的标准方法是 vanilla循环for

var length = arr.length,
    element = null;
for (var i = 0; i < length; i++) {
  element = arr[i];
  // Do something with element
}

但是请注意,这种方法仅适用于密集数组,并且每个索引都被一个元素占用。如果数组是稀疏的,那么使用这种方法可能会遇到性能问题,因为您将迭代数组中并不真正存在的许多索引在这种情况下,for .. in-loop 可能是一个更好的主意。但是,您必须使用适当的保护措施来确保仅对数组的所需属性(即数组元素)进行操作,因为for..in-loop 也将在旧浏览器中枚举,或者如果附加属性定义为enumerable.

ECMAScript 5 中,数组原型上会有一个 forEach 方法,但在旧版浏览器中不支持。因此,为了能够始终如一地使用它,您必须有一个支持它的环境(例如,用于服务器端 JavaScript 的Node.js),或者使用“Polyfill”。然而,此功能的 Polyfill 是微不足道的,并且由于它使代码更易于阅读,因此它是一个很好的 polyfill。

有没有办法在一行代码中做到这一点。例如,在 facebook 中,我喜欢使用document.getElementsByTagName("video")[28].playbackRate = 2.2. 如果我可以轻松地映射所有元素,那么我就可以避免识别哪个视频(例如在这种情况下索引 28)。有任何想法吗?
2021-02-13 22:56:12
@stevec: Array.from(document.querySelectorAll('video')).forEach(video => video.playbackRate = 2.2);
2021-03-05 22:56:12

如果您使用的是jQuery库,则可以使用jQuery.each

$.each(yourArray, function(index, value) {
  // do your stuff here
});

编辑 :

根据问题,用户想要使用 javascript 而不是 jquery 的代码,因此编辑是

var length = yourArray.length;   
for (var i = 0; i < length; i++) {
  // Do something with yourArray[i].
}

向后循环

我认为这里的反向循环值得一提:

for (var i = array.length; i--; ) {
     // process array[i]
}

好处:

  • 您不需要声明一个临时len变量,也不需要array.length在每次迭代时进行比较,这两者都可能是一个微小的优化。
  • 以相反的顺序从 DOM 中删除同级通常更有效(浏览器需要在其内部数组中进行较少的元素移动。)
  • 如果您在循环时在索引i处或之后修改数组(例如,您在 处删除或插入一个项目),则前向循环将跳过左移到位置i 的项目,或重新处理第i个项目右移。在传统的 for 循环中,您可以更新i以指向需要处理的下一项 - 1,但简单地反转迭代方向通常是一个更简单更优雅的解决方案array[i]
  • 同样,在修改或移除嵌套的DOM 元素时,反向处理可以避免错误例如,考虑在处理其子节点之前修改父节点的 innerHTML。到达子节点时,它将与 DOM 分离,在编写父节点的 innerHTML 时已被新创建的子节点替换。
  • 这是更短的录入,读取,比一些可用的其他选项。虽然它输给forEach()了 ES6 的for ... of.

缺点:

  • 它以相反的顺序处理项目。如果您正在根据结果构建一个新数组,或者在屏幕上打印内容,那么输出自然与原始顺序相反
  • 重复将兄弟姐妹作为第一个孩子插入 DOM 以保持它们的顺序效率较低(浏览器将不得不继续正确地移动。)为了高效有序地创建 DOM 节点,只需像往常一样向前循环并追加(并且还使用“文档片段”)。
  • 反向循环让初级开发人员感到困惑(您可能认为这是一种优势,这取决于您的观点。)

我应该一直使用它吗?

一些开发人员默认使用反向 for 循环,除非有充分的理由向前循环。

虽然性能提升通常微不足道,但它有点尖叫:

“对列表中的每一项都这样做,我不在乎顺序!”

然而在实践中是实际的意图的可靠指标,因为它是从这些场合没有区别,当你对井井有条,真的需要循环反向。因此,实际上需要另一种结构来准确表达“无关紧要”的意图,目前大多数语言(包括 ECMAScript)中都无法使用这种结构,但可以将其称为,例如forEachUnordered().

如果顺序无关紧要,而效率是一个问题(在游戏或动画引擎的最内层循环中),那么使用反向 for 循环作为首选模式可能是可以接受的。请记住,在现有代码看到反向 for 循环并不一定意味着顺序无关紧要!

最好使用 forEach()

一般来说,对于更关注清晰度和安全性的更高级别的代码,我以前建议使用Array::forEach作为循环的默认模式(尽管现在我更喜欢使用for..of)。首选forEach反向循环的原因是:

  • 读起来更清晰。
  • 这表明,是不会被块(总是在长的可能的惊喜隐藏内移动forwhile循环)。
  • 它为您提供了一个免费的闭包范围。
  • 它减少了局部变量的泄漏以及与外部变量(和突变)的意外碰撞。

然后,当您在代码中看到反向 for 循环时,这表明它被反转是有充分理由的(也许是上述原因之一)。看到传统的前向循环可能表明可以发生转移。

(如果对意图的讨论对您没有意义,那么您和您的代码可能会从 Crockford 的关于编程风格和您的大脑的讲座中受益。)

现在使用 for..of 更好!

有一个关于是否辩论for..of或者forEach()是较好的:

  • 为了获得最大的浏览器支持,迭代器for..of 需要一个 polyfill,使您的应用程序执行速度稍慢,下载速度稍大。

  • 出于这个原因(并鼓励使用mapfilter),一些前端风格指南for..of完全禁止

  • 但是上述问题不适用于 Node.js 应用程序,for..of现在已经很好地支持了 Node.js 应用程序

  • 而且里面await 也不行forEach()在这种情况下,使用for..of最清晰的模式

就个人而言,我倾向于使用看起来最容易阅读的内容,除非性能或缩小已成为主要问题。因此,这些天我更喜欢使用for..of代替forEach(),但在适用时我将始终使用maporfilterfindor some(为了同事,我很少用reduce。)


它是如何工作的?

for (var i = 0; i < array.length; i++) { ... }   // Forwards

for (var i = array.length; i--; )    { ... }   // Reverse

你会注意到这i--是中间的子句(我们通常看到比较的地方),最后一个子句是空的(我们通常看到的地方i++)。这意味着这i--也用作继续条件至关重要的是,它每次迭代之前执行和检查

  • 怎么开始array.length不爆炸?

    因为每次迭代之前i--运行,所以在第一次迭代时,我们实际上将访问避免数组越界项目出现任何问题的项目。array.length - 1 undefined

  • 为什么它不在索引 0 之前停止迭代?

    当条件i--评估为 falsey 值(当它产生 0 时)时,循环将停止迭代

    诀窍是与 不同--i,尾随i--运算符递减i但产生递减之前您的控制台可以证明这一点:

    > var i = 5; [i, i--, i];

    [5, 5, 4]

    所以在最后一次迭代中,i以前是1并且i--表达式将其更改为0但实际上产生了1(真实),因此条件通过。在下一次迭代中,i--i更改-1,但产生0(falsey),导致执行立即退出循环底部。

    在传统的 for 循环中,i++++i是可以互换的(正如 Douglas Crockford 指出的那样)。然而在相反的 for 循环中,因为我们的递减也是我们的条件表达式,i--如果我们想处理索引 0 处的项目,我们必须坚持下去


琐事

有些人喜欢在反向for循环中画一个小箭头,并以眨眼结束:

for (var i = array.length; i --> 0 ;) {

感谢 WYL 向我展示了反向 for 循环的好处和可怕之处。

一些C风格的语言使用foreach循环遍历枚举。在 JavaScript 中,这是通过for..in循环结构完成的

var index,
    value;
for (index in obj) {
    value = obj[index];
}

有一个问题。for..in将遍历对象的每个可枚举成员及其原型上的成员。为了避免读取通过对象原型继承的值,只需检查该属性是否属于该对象:

for (i in obj) {
    if (obj.hasOwnProperty(i)) {
        //do stuff
    }
}

此外,ECMAScript 5添加了一个forEach方法Array.prototype,可用于使用回调枚举数组(polyfill 位于文档中,因此您仍然可以将其用于旧浏览器):

arr.forEach(function (val, index, theArray) {
    //do stuff
});

重要的是要注意Array.prototype.forEach回调返回时不会中断falsejQueryUnderscore.js提供了它们自己的变体each来提供可以短路的循环。