使用JavaScript中的for…of语法获取循环计数器/索引

656

注意:

这个问题依然适用于for...of循环。 不要使用for...in来遍历一个数组,而是用它来遍历一个对象的属性。话虽如此,这段代码


我理解在JavaScript中基本的for...in语法如下:

for (var obj in myArray) {
    // ...
}

但是我该如何获取循环的计数器/索引?

我知道我可能可以做类似以下的事情:

var i = 0;
for (var obj in myArray) {
    alert(i)
    i++
}

甚至还有这个老方法:

for (var i = 0; i < myArray.length; i++) {
    var obj = myArray[i]
    alert(i)
}

但我更愿意使用更简单的for-in循环。我认为它们看起来更好,并且更有意义。

是否有更简单或更优雅的方法?


在Python中,这很容易:

for i, obj in enumerate(myArray):
    print i

11
不要对数组使用for...in语句。而且,它遍历的是属性名而不是属性值。 - Felix Kling
4
这是一个数组而不是对象,对吗?所以,是alert(obj)吗? - gen_Eric
12个回答

1061

for…in 循环遍历的是属性名,而不是值,并且顺序不确定(即使在 ES6 后也是如此)。你不应该使用它来遍历数组。对于数组,可以使用 ES5 的forEach 方法,该方法将值和索引都传递给你提供的函数:

var myArray = [123, 15, 187, 32];

myArray.forEach(function (value, i) {
    console.log('%d: %s', i, value);
});

// Outputs:
// 0: 123
// 1: 15
// 2: 187
// 3: 32

或者是ES6的Array.prototype.entries,它现在已经在当前浏览器版本中得到支持:

for (const [i, value] of myArray.entries()) {
    console.log('%d: %s', i, value);
}

对于通用可迭代对象(您将使用 for…of 循环而不是 for…in 循环的情况),没有内置方法,但是可以使用 for…of 循环:

function* enumerate(iterable) {
    let i = 0;

    for (const x of iterable) {
        yield [i, x];
        i++;
    }
}

for (const [i, obj] of enumerate(myArray)) {
    console.log(i, obj);
}

如果你实际上是指枚举属性,那么你需要一个额外的计数器。可以使用Object.keys(obj).forEach,但它只包括自己的属性;而for…in则包括原型链上任何可枚举的属性。

3
@quantumpotato:let是具有块级作用域的varconst是不可变的。 - Ry-
我不知道这是可能的: myArray.forEach(function (value, *i*) (重点在于我)。这太完美了。我知道一定有比我一直在做的那个 myArray.indexOf(value) 垃圾更好的方法。+1。 - Braden Best
2
这是一个愚蠢的问题,但%d和%s实际上代表什么?或者它们可以是我想要的任何字母吗? - klewis
2
@klewis:%d 格式化整数,%s 格式化字符串。它们受 printf 的启发。规范正在 https://console.spec.whatwg.org/#formatter 进行中。 - Ry-
2
forEach 的缺点是,内部的 await 作用域限定在函数参数中,而不是外部作用域。因此,如果你想在循环内部使用 await,你可能需要使用 .entries() - user1338062
显示剩余10条评论

442

在ES6中,使用for...of循环是一个很好的选择,可以像这样获取for...of中的索引。

for (let [index, val] of array.entries()) {
  // your code goes here    
}
请注意,Array.entries()返回一个迭代器,这使得它可以在for-of循环中使用;不要将其与返回键值对数组Object.entries()混淆。

6
我认为这个解决方案比forEach更好...它使用正常的for...of循环语法,而且你不需要使用单独的函数。换句话说,它在语法上更好。原帖似乎想要这个。 - u8y7541
2
它应该这样做,Joshua - 这个对象是一个迭代器,一个带有 next() 方法的对象,每次调用它都会返回数组中后续的条目。它里面没有(可见的)数据;你通过调用 next() 来获取底层对象中的数据,而 for-of 在幕后执行此操作。抄送 @tonyg - Shog9
6
同时,这使得await能够按顺序工作,而forEach则不能。 - geoidesic
2
请注意,这仅适用于Arrayfor...of适用于任何Iterable。您可能需要使用for (const [index, value] of [...array].entries())将其转换为Array。可迭代对象包括HTMLCollectionNodeList和其他DOM类。 - ShortFuse
1
为什么要使用 let 而不是 const - Ben
显示剩余5条评论

53

这个怎么样?

let numbers = [1,2,3,4,5]
numbers.forEach((number, index) => console.log(`${index}:${number}`))

array.forEach 这个方法有一个 index 参数,它表示当前在数组中处理的元素的索引。


10
被选中的答案发布于6年之前,已经包含了相同的信息…… - Deiv
5
由于break不可用,因此foreach循环不利于优化。 - Zhong Ri
@smartworld-dm 我同意这个观点,但是还有一个简单的返回语句。 - sld

22

8
我建议:使用计数器,在循环中递增它。 - mayankcpdixit
4
继mayankcpdixit所说,最好使用计数器而不是indexOf,因为indexOf可能会对性能产生负面影响。 - Dean Liu
3
物体越大,速度就会变慢。但这个规律并不是线性的。 - Dennis Hackethal
3
这种写法有点毫无意义,而且过于缓慢和复杂了。因为 var i = 0;i++; 更短更高效。此外,它不适用于不是自身属性的可枚举属性。 - Ry-
1
@trusktr:如果需要的话……你仍然不应该使用这个。在修改集合时只需修改计数器即可。如果不一定非得原地修改,可以进行一个漂亮的函数式转换。 - Ry-
显示剩余11条评论

17

For-in循环遍历对象的属性。不要在数组上使用它们,即使它们有时可以工作。

对象属性没有索引,它们都是相等的,并且不需要按照确定的顺序运行。如果您想计算属性数,您将不得不设置额外的计数器(就像您在第一个示例中所做的那样)。

遍历数组:

var a = [];
for (var i=0; i<a.length; i++) {
    i // is the index
    a[i] // is the item
}

遍历对象:

var o = {};
for (var prop in o) {
    prop // is the property name
    o[prop] // is the property value - the item
}

3
永远不要直接使用(var i=0; i<a.length; i++),因为这会浪费资源。应该使用(var i=0, var len = a.length; i<len; i++) - Félix Sanz
27
@FelixSanz:浪费资源?不可能。这是一种过早的微优化,几乎从来不需要,并且 var i=0; i<a.length; i++) 是标准的循环模式,在任何好的 JavaScript 引擎中都进行了优化。 - Bergi
5
@FelixSanz:是的,var i=0; i<a.length; i++ 是最佳实践。 - Bergi
1
KISS。如果你写循环确实需要这个,那么要么你做错了什么,要么你必须有比“最佳实践”更好的理由来证明它的必要性。是的,这是一种标准实践,但不是为了通用性能优化,而只是为了微观优化。 - Bergi
4
KISS 原则无处不在。过早优化是一种反实践行为。 - Bergi
显示剩余2条评论

11

正如其他人所说,你不应该使用for..in来迭代数组。

for ( var i = 0, len = myArray.length; i < len; i++ ) { ... }

如果你想要更简洁的语法,你可以使用forEach:

myArray.forEach( function ( val, i ) { ... } );

如果您想使用此方法,请确保包含ES5 shim以添加对旧版浏览器的支持。


8

rushUp 给出的答案是正确的,但这种方法更加方便。

for (let [index, val] of array.entries() || []) {
   // your code goes here    
}

3
|| []是不必要的,并且永远不会被使用;array.entries()总是真值。 - Ry-
[index, val] 对我来说从来没有起作用,它会显示“未定义”。 - Barbz_YHOOL
你能分享一下你的数组吗? - Renish Gotecha

3
除了大家提供的非常好的答案外,我想补充一点,最高效的解决方案是ES6的entries。这似乎对许多开发人员来说很不直观,因此我创建了这个性能基准测试

enter image description here

它的速度快了约6倍。主要原因是不需要:a)多次访问数组,b)转换索引。


2
我必须说,在上述测试用例中,你没有进行苹果与苹果的比较。在经典模式下,额外的const v被定义加上不必要的类型转换Number(i),这些都导致了它的开销。通过去除这些部分,我的结果显示相反的情况:经典模式快了4倍。请查看更新版本此处 - Marshal
@Marshal,你的链接已经失效了。 - WestCoastProjects
@javadba,那是因为jsperf已经关闭。我会创建一个新的答案。 - Marshal
说它是“最高效的解决方案”,基于一个只包括另一种方法(也恰巧是错误的)的基准测试,这是相当误导人的。为什么不与顶级答案进行比较呢? - Ry-

2
这是我自己的一个复合迭代器版本,它返回一个索引和任何传入的生成器函数的值,并附带一个(慢速)素数搜索的示例。

const eachWithIndex = (iterable) => {
  return {
    *[Symbol.iterator]() {
      let i = 0
      for(let val of iterable) {
        i++
          yield [i, val]
      }
    }
  }

}

const isPrime = (n) => {
  for (i = 2; i < Math.floor(Math.sqrt(n) + 1); i++) {
    if (n % i == 0) {
      return false
    }
  }
  return true
}

let primes = {
  *[Symbol.iterator]() {
    let candidate = 2
    while (true) {
      if (isPrime(candidate)) yield candidate
        candidate++
    }
  }
}

for (const [i, prime] of eachWithIndex(primes)) {
  console.log(i, prime)
  if (i === 100) break
}


为什么你有一个名为 eachWithIndex[Symbol.iterator] 的函数,而不是一个名为 eachWithIndex 的函数?eachWithIndex 不满足可迭代接口,这也正是 Symbol.iterator 的全部意义所在。 - Ry-
@Ry- 很好的发现,已经将 eachWithIndex 改为接受可迭代对象并返回闭合的复合迭代器。 - akurtser

2
这是一个名为eachWithIndex的函数,可以与任何可迭代对象一起使用。
您也可以编写类似的函数eachWithKey,使用for...in可以处理对象。
"Original Answer"翻译成“最初的回答”。
// example generator (returns an iterator that can only be iterated once)
function* eachFromTo(start, end) { for (let i = start; i <= end; i++) yield i }

// convers an iterable to an array (potential infinite loop)
function eachToArray(iterable) {
    const result = []
    for (const val of iterable) result.push(val)
    return result
}

// yields every value and index of an iterable (array, generator, ...)
function* eachWithIndex(iterable) {
    const shared = new Array(2)
    shared[1] = 0
    for (shared[0] of iterable) {
        yield shared
        shared[1]++
    }
}

console.log('iterate values and indexes from a generator')
for (const [val, i] of eachWithIndex(eachFromTo(10, 13))) console.log(val, i)

console.log('create an array')
const anArray = eachToArray(eachFromTo(10, 13))
console.log(anArray)

console.log('iterate values and indexes from an array')
for (const [val, i] of eachWithIndex(anArray)) console.log(val, i)

生成器的好处在于它们是惰性的,并且可以将另一个生成器的结果作为参数。"最初的回答"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接