There’s numerous ways to loop over arrays and objects in JavaScript, each with their own advantages and disadvantages. Some guidelines (like AirBnB’s) recommend to avoid looping functions in favor of native constructs. In this article, I’ll try to explain the differences between the main array iterators: for loop, for…in, for…of and .forEach().
Overview
Let’s start with the simplest of the four: for
and for/in
give you access to the index in the array, not the actual element. That means with for
and for/in
, you need to print out arr[i]
to show the elements.
const arr = ['a', 'b', 'c'];
for (let i = 0; i < arr.length; ++i) {
// 0, 1, 2
console.log(i);
// a, b, c
console.log(arr[i]);
}
for (let i in arr) {
// 0, 1, 2
console.log(i);
// a, b, c
console.log(arr[i]);
}
With the other two constructs, forEach()
and for/of
, you obtain the array element itself.
The main difference between the 2 is taht with forEach()
you can access the array index i
, whaile with for/of
you can’t.
const arr = ['a', 'b', 'c'];
arr.forEach((v) => console.log(v));
for (const v of arr) { console.log(v); }
// a, b, c
arr.forEach((v, i) => console.log(i));
// 0, 1, 2
Arrays are objects
JavaScript arrays are just objects. Special, ones but still objects. That means you can add any kind of properties to them:
const arr = ['a', 'b', 'c'];
typeof arr; // 'object'
// Assign to a non-numeric property
arr.prop = 'test';
console.log(arr.prop); // 'test'
arr[1] === arr['1']; // true,
3 of the 4 looping constructs ignore the non-numeric property. However, `for/in` will actually print out "test". This is why, as a rule of thumb, iterating through an array using `for/in` is generally a bad practice. So better to go for one of the other three loops:
const arr = ['a', 'b', 'c'];
arr.prop = 'test';
// Prints "a, b, c"
for (let i = 0; i < arr.length; ++i) { console.log(arr[i]); }
// Prints "a, b, c"
arr.forEach((el, i) => console.log(i, el));
// Prints "a, b, c"
for (const el of arr) { console.log(el); }
// Prints "a, b, c, test"
for (const el in arr) { console.log(arr[el]); }
Empty Elements
In JavaScript, arrays can contain empty elements. Now, in case of empty arrays each looping construct has its own behavior:
for/in
andforEach
skip the empty elementfor
andfor/of
handle it asundefined
All loop constructs handle the undefined
element as expected:
const arr = ['a',,'c',undefined];
// Prints "a, undefined, c, undefined"
for (const v of arr) { console.log(v); }
for (let i = 0; i < arr.length; ++i) {
console.log(arr[i]);
}
// Prints "a, c, undefined"
arr.forEach(v => console.log(v));
for (let i in arr) { console.log(arr[i]); }
Function Context
Function context refers to how the value of this
is assigned when a function is called. This(!) is a very complex topic and explaining it is outside the scope of this article. What’s important to understand about it is that for
, for/in
, and for/of
, being language built-in constructs, behave as described in the official documentation. However, the forEach()
loop is a function, and its function context may behave the same way unless you use an arrow function. In this case, even with strict, this
will default to the outside function scope:
'use strict';
const arr = ['a'];
// Prints "undefined"
arr.forEach(function() {
console.log(this);
});
// Prints [object Object]
arr.forEach(()=> {
console.log(this);
});
Promises
While the native for/loop constructs work fine with promises and async functions, forEach()
doesn’t:
async function func() {
const arr = ['a', 'b', 'c'];
arr.forEach(el => {
// SyntaxError
await new Promise(res => setTimeout(res, 200));
console.log(el);
});
}
The above example work as expected with for/of
:
async function func() {
const arr = ['a', 'b', 'c'];
for(const el of arr) {
// SyntaxError
await new Promise(res => setTimeout(res, 200));
console.log(el);
});
}
So I recommend to use .forEach()
loops only for simple, synchronous code.
Performance
Ok, and here we are. In the section most people would be interested in. For the test, I decided to use Mozilla’s internal console.time()
function. Final times were not always the same, but results did, so i believe it’s quite reliable. Being interested in the loop performance only, I decided to iterate a 50k items array with an empty loop. here’s the code I used an you can replicate in your own browser:
arr = typeof arr == 'undefined' ? [...Array(50000).keys()] : arr;
console.time('for');
for(let i = 0; i < arr.length; i++){}
console.timeEnd('for')
console.time('for...in');
for(const i in arr){}
console.timeEnd('for...in')
console.time('for...of');
for(const el of arr){}
console.timeEnd('for...of')
console.time('forEach');
arr.forEach(el=>{})
console.timeEnd('forEach')
And these are the results:
for: 8ms - timer ended
for…in: 9ms - timer ended
for…of: 3ms - timer ended
forEach: 2ms - timer ended
As said earlier, the results for each method varied by one or two ms each time. I repeted the test inverting all block positions and the result didn't change: `.forEach` was the clear winner, folowed by the `for...of` construct:
forEach: 2ms - timer ended
for…of: 5ms - timer ended
for…in: 11ms - timer ended
for: 8ms - timer ended
Conclusions
My personal opinion is for/of
should be the general way to iterate over an array in JavaScript. It is quite fast, intuitive and doesn’t suffer from the issues for/in
and forEach()
suffer. The major downside of for/of
is that you need to write some extra code in case you need the index. But you can easily work around this in a simple way.
const arr = ['a','b','c'];
for (const [i, el] of arr.entries()) {
console.log(i, el);
// Prints "0 a", "1 b", "2 c"
}
As a side note, you can optimize the for loop in 2 main ways: minimizing property lookups and reversing array order
const arr = ['a','b','c'];
// minimizing property lookup
for (let i=0, let len=arr.length; i < len; i++) {
console.log(arr[i]);
}
// minimizing property lookups and reversing array order
for (let i = arr.length; i--;) {
console.log(arr[i]);
}
I didn’t measure this variation, but I believe the readability and simplicity of for...of
makes it still the way to go, unless you really need to improve performance. In this case, you can use .forEach
unless you’re writing async code.