I was reading airbnb javascript guide. There is a particular statement, that says:
Don’t use iterators. Prefer JavaScript’s higher-order functions ins
The below is a list of advantages I see between the two.
forEach
, you need to change which stack-entry you're in to see variables not accessed within the loop function)break
or continue
directly, instead of needing to use the some
function and doing return true
, which reduces readability. (can be emulated)await
within the loop, with the async/await context preserved. (can be emulated)map
and filter
, so it's easier to convert between the two.break
, continue
, return
, and even async
/await
)for-of
is 5 characters more if using const
-- 3 if using let
/var
)This reasoning in Airbnb style guide applies to array methods that are used for immutability, which are filter
, map
, reduce
, etc. but not forEach
:
This enforces our immutable rule. Dealing with pure functions that return values is easier to reason about than side effects.
So the comparison is more like:
// bad
let sum = 0;
for (let num of numbers) {
sum += num;
}
sum === 15;
// bad
let sum = 0;
numbers.forEach((num) => {
sum += num;
});
sum === 15;
// good
const sum = numbers.reduce((num, sum) => sum += num, 0);
sum === 15;
Generally, for > forEach > for..of > for..in
in terms of performance. This relationship is uniform in almost all engines but may vary for different array lengths.
forEach
is the one that was significantly improved in latest Chrome/V8 (almost twice, based on this synthetic test):
Since all of them are fast, choosing less appropriate loop method just because of performance reasons can be considered preliminary optimization, unless proven otherwise.
The main benefits of forEach
in comparison with for..of
is that the former be polyfilled even in ES3 and provides both value and index, while the latter is more readable but should be transpiled in ES5 and lower.
forEach
has known pitfalls that make it unsuitable in some situations that can be properly handled with for
or for..of
:
callback function creates new context (can be addressed with arrow function)
doesn't support iterators
doesn't support generator yield
and async..await
doesn't provide a proper way to terminate a loop early with break
Majority of the time, the Airbnb styleguide tries to keep things consistent. This doesn't mean that there are never reasons to use a for loop or a for-in loop, like suppose you want to break out of the loop out early.
The immutability comes into play when using a for loop to the change the value of something. Like reducing an array to an integer or mapping over elements to generate a new array. Using a built-in map
, reduce
, or filter
, will not directly mutate the array's value directly so it's preferred. Using forEach
over a for/for-in loop enforces the style consistency of using a higher order function over an iterator, so I believe that's why it's being recommended.
That makes no sense. forEach
should never be preferred. Yes, using map
and reduce
and filter
is much cleaner than a loop that uses side effects to manipulate something.
But when you need to use side effects for some reason, then for … in
and for … of
are the idiomatic loop constructs. They are easier to read and just as fast, and emphasise that you have a loop body with side effects in contrast to forEach
which looks functional with its callback but isn't. Other advantages over forEach
include that you can use const
for the iterated element, and can use arbitrary control structures (return
, break, continue
, yield, await) in the loop body.
forEach
iterations cannot be delayed by await
. This means you cannot use forEach
to pipeline, let's say, web requests to a server.
Also forEach
is not present on async Iterables
.
Consider the following cases:
let delayed = (value)=>new Promise(resolve => setTimeout(() => resolve(value), 2000));
(async ()=>{
for(el of ['d','e','f'])console.log(await delayed(el))
})();
(async()=>{
['a','b','c'].forEach(async (el)=>console.log(await delayed(el)))
})();
Result:
d
a
b
c
e
f
The elements of [d,e,f]
array are printed every two seconds.
The elements of [a,b,c]
array are printed all together after two seconds.
let delayed = (value)=>new Promise(resolve => setTimeout(() => resolve(value), 2000));
async function* toDelayedIterable(array) {
for(a of array)yield (await delayed(a))
}
for await(el of toDelayedIterable(['d','e','f']))console.log(el)
toDelayedIterable(['a', 'b', 'c']).forEach(async(el)=>console.log(await el));
Result:
d
e
f
Uncaught TypeError: toDelayedIterable(...).forEach is not a function
The elements of [d,e,f]
array are printed every two seconds.
Error when trying to access forEach
on the asyncIterator
of [a,b,c]
array.
see the Performance of foreach and forloop here
only for readablity we are using foreach. Other than that for loop is prefered for browser compatibality ,performance and native feel.