javascriptfor-loopforeachcounter

Get loop counter/index using for…of syntax in JavaScript


I understand that the basic for...of syntax in JavaScript looks like this:

for (let obj of myArray) {
  // ...
}

But how do I get the loop counter/index when iterating with this syntax?

(With the same question applying to for...in notation for iterating over object property names)

I know I can use an explicit loop counter like:

for (let i = 0; i < myArray.length; i++) {
  const obj = myArray[i];
  console.log(i);
}

Or manually track the index outside of the loop:

let i = 0;
for (let obj of myArray) {
  console.log(i);
  i++;
}

But I would rather use the simpler for...of loop, I think they look better and make more sense.

As an example of a language that lets you do this, in Python it's as easy as:

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

Solution

  • for…in iterates over property names, not values (and did so in an unspecified order up until ES2020*). You shouldn’t use it to iterate over arrays. For them, there’s ES6’s Array.prototype.entries, which now has support across current browser versions:

    const myArray = [123, 15, 187, 32];
    
    for (const [i, value] of myArray.entries()) {
      console.log(`${i}: ${value}`);
    }
    
    // 0: 123
    // 1: 15
    // 2: 187
    // 3: 32
    .as-console-wrapper { max-height: 100% !important; top: 0; border-top: 0 !important; }

    Or, for extended compatibility with older browsers, there’s ES5’s forEach method that passes both the value and the index to the function you give it:

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

    For iterables in general (where you would use a for…of loop rather than a for…in), iterator helpers are now in the language. You can use Iterator.prototype.forEach to iterate over an entire iterable with an index:

    function* fibonacci() {
      let a = 0;
      let b = 1;
    
      for (;;) {
        yield a;
        [a, b] = [b, a + b];
      }
    }
    
    fibonacci().take(10).forEach((x, i) => {
      console.log(`F_${i} = ${x}`);
    });
    .as-console-wrapper { max-height: 100% !important; top: 0; border-top: 0 !important; }

    More generally, Iterator#map can associate the values yielded by an iterator with their indexes:

    fibonacci().map((x, i) => [i, x])
    

    Not every iterable (or iterator!) is an Iterator, but you can convert every iterable to an Iterator with Iterator.from.

    Without support for iterator helpers, you can use a generator function instead:

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

    If you actually did mean for…in – enumerating properties – you would need an additional counter. Object.keys(obj).forEach could work, but it only includes own properties; for…in includes enumerable properties anywhere on the prototype chain.

    * The order is still unspecified under certain circumstances, including for typed arrays, proxies, and other exotic objects, as well as when properties are added or removed during iteration.