How to get for...of loop index in JavaScript

Jun 29, 2023#javascript#how-to

The for…of loop is a JavaScript statement that executes a block of code for each element of an iterable object, such as an array, a string, a map, a set, or a generator. For example, you can use a for…of loop to iterate over the elements of an array like this:

const fruits = ['apple', 'banana', 'cherry'];

for (const fruit of fruits) {
  console.log(fruit);
}

// apple
// banana
// cherry

The for…of loop is different from the for…in loop, which iterates over the property names (or keys) of an object, not the values. It is also different from the Array.prototype.forEach() method, which executes a callback function for each element in the array.

const person = {name: "Alice", age: 25, occupation: "programmer"};
for (let prop in person) {
  console.log(prop + ": " + person[prop]);
}
// name: Alice
// age: 25
// occupation: programmer

const numbers = [1, 2, 3];
numbers.forEach(function(number) {
  console.log(number * 2);
});
// 2
// 4
// 6

Some advantages of using the for…of statement are:

  • It works with any iterable object, not just arrays.
  • It is more concise and readable than a conventional for loop.
  • It allows you to use break, continue, and return statements inside the loop body.
  • It avoids the pitfalls of using for…in on arrays, such as iterating over non-index properties or prototype methods.

However, the for…of loop does not provide the index of the element by default. If you need the index, you can use one of the following methods:

  1. You can use the Array.prototype.entries() method, which returns an iterator that yields pairs of [index, value] for each element in the array. For example:
const fruits = ['apple', 'banana', 'cherry'];

for (const [index, fruit] of fruits.entries()) {
  console.log(`index: ${index}, fruit: ${fruit}`);
}

// index: 0, fruit: apple
// index: 1, fruit: banana
// index: 2, fruit: cherry
  1. If the array only has unique elements, you can use the Array.prototype.indexOf() method, which returns the first index at which a given element can be found in the array, or -1 if it is not present. For example:
const fruits = ['apple', 'banana', 'cherry'];

for (const fruit of fruits) {
  const index = fruits.indexOf(fruit);
  console.log(`index: ${index}, fruit: ${fruit}`);
}

// index: 0, fruit: apple
// index: 1, fruit: banana
// index: 2, fruit: cherry

Note that this method is less efficient than the previous one, as it requires searching the array on each iteration. If the array contains duplicate elements then it won’t work as you expected:

const fruits = ['apple', 'banana', 'banana', 'cherry'];

for (const fruit of fruits) {
  const index = fruits.indexOf(fruit);
  console.log(`index: ${index}, fruit: ${fruit}`);
}

// index: 0, fruit: apple
// index: 1, fruit: banana
// index: 1, fruit: banana
// index: 3, fruit: cherry
  1. You can use a separate counter variable and increment it on each iteration. For example:
const fruits = ['apple', 'banana', 'cherry'];

let index = 0;
for (const fruit of fruits) {
  console.log(`index: ${index}, fruit: ${fruit}`);
  index++;
}

// index: 0, fruit: apple
// index: 1, fruit: banana
// index: 2, fruit: cherry

What is iterable object?

An iterable object in JavaScript is an object that can be iterated over with the for…of statement. It means that the object has a special method called Symbol.iterator that returns an iterator object.

An iterator object is an object that has a next() method that returns an object with two properties: value and done. The value property holds the current value of the iteration, and the done property is a boolean that indicates whether the iteration is finished or not.

Some built-in objects in JavaScript are iterable by default, such as arrays, strings, maps, sets, and generators. For example:

const array = [1, 2, 3];
const string = "hello";
const map = new Map([["a", 1], ["b", 2]]);
const set = new Set([1, 2, 3]);
function* generator() {
  yield 1;
  yield 2;
  yield 3;
}

// These objects are all iterable
for (let x of array) console.log(x); // 1, 2, 3
for (let x of string) console.log(x); // h, e, l, l, o
for (let x of map) console.log(x); // ["a", 1], ["b", 2]
for (let x of set) console.log(x); // 1, 2, 3
for (let x of generator()) console.log(x); // 1, 2, 3

However, some objects are not iterable by default, such as plain objects or numbers. For example:

const object = {name: "Alice", age: 25};
const number = 42;

// These objects are not iterable
for (let x of object) console.log(x); // TypeError: object is not iterable
for (let x of number) console.log(x); // TypeError: number is not iterable

To make an object iterable, we need to define the Symbol.iterator method on it and return an iterator object from it. For example:

const range = {
  from: 1,
  to: 5,
};

// Define the Symbol.iterator method
range[Symbol.iterator] = function () {
  // Return the iterator object
  return {
    current: this.from,
    last: this.to,
    // Define the next() method
    next() {
      // Return the value and done properties
      if (this.current <= this.last) {
        return { value: this.current++, done: false };
      } else {
        return { done: true };
      }
    },
  };
};

// Now range is iterable
for (let x of range) console.log(x); // 1, 2, 3, 4, 5

The pitfalls of using for…in on arrays

The for…in statement in JavaScript is used to loop over the property keys of an object. It is not recommended to use it on arrays, because it has some pitfalls, such as:

  • It does not guarantee the order of iteration, which may be different from the array order.
  • It iterates over all enumerable properties of the array object, including those inherited from the prototype chain or added by user-defined methods.
  • It returns the index as a string, not a number, which may cause unexpected behavior when using arithmetic operations.

For example:

const array = [1, 2, 3];
array.foo = "bar"; // add a non-index property
Array.prototype.baz = "qux"; // add a prototype property

for (let x in array) {
  console.log(x); // 0, 1, 2, foo, baz
  console.log(x + 1); // 01, 11, 21, foo1, baz1
}

As you can see, the for…in loop iterates over the non-index properties and the prototype property, and also returns the index as a string. This may not be what you want when looping over an array.