Cheatsheet: JS loops (forEach, for/in, for/of)

Sylwia Vargas - Feb 1 '21 - - Dev Community

tl;dr the best use for these loops is as follows:

  • arrays: use for... of (or forEach)
  • objects: use for... in
  • if you work with arrays and need an access to indexes, use for or forEach

I remember this as A ray of sunshine and foreign objects in the sky, which reminds me that:

  • Array -> for/of
  • Object -> for/in

for... in loop

This loop was introduced in ES6, as a tool to use with enumerables β€”- to iterate over the properties of the object:

let person = {name: "SpongeBob", lastName: "SquarePants", age:  34}

for (let property in person) {
  console.log(`${property}: ${person[property]}`);
}

///// RESULT:
// "name: SpongeBob"
// "lastName: SquarePants"
// "age: 34"

Enter fullscreen mode Exit fullscreen mode

Now, you can iterate over an array with this tool but that doesn't mean you should. It works because in JS arrays inherit from the Object and because of that, the bracket notation ([]) will have a similar effect if you call this looping tool on an array as it has on an object. However, there are two caveats: arrays impact on speed and situations when you're dealing with a NodeList or just inconsistent data in an array.

As for speed, the situation is clear: when iterating over arrays, for... in is much slower than the for... of loop that is specific to arrays, strings and NodeLists.

Now, in order to understand the issues with inconsistent data, you need to remember that Array is an Object and as such, you can assign to it properties (which should not happen but may):

let students = ["SpongeBob", "Patrick"]
students.name = "Mr. Krabs"

students
// ["SpongeBob", "Patrick", name: "Mr. Krabs"]
Enter fullscreen mode Exit fullscreen mode

Now, this is an example of inconsistent data and we would wish that our loop would just disregard it. However, for... in was created to iterate over properties and so it will do just that:

let students = ["SpongeBob", "Patrick"]
students.name = "Mr. Krabs"

for (let element in students){
  console.log(`${element} is ${students[element]}`)
}

//// RESULTS
// "0 is SpongeBob"
// "1 is Patrick"
// "name is Mr. Krabs"
Enter fullscreen mode Exit fullscreen mode

Now, this is especially important when it comes to e.g. NodeLists that look like an array but in fact also hold a bunch of other elements (e.g. methods) and it's best if they were skipped altogether.


for... of loop

Also introduced in ES6, for... of is specific to iterables: arrays, strings, NodeLists, sets and maps. It comes with a great set of features:

  1. it checks whether an element is iterable (using GetIterator operation) and throws an appropriate TypeError if it's not:

    let num = 3
    for (let i of num){
     console.log("hi")
    }
    
    /// RESULT:
    // Uncaught TypeError: num is not iterable
    

    Compare it with for... in (which you should not use on arrays):

    let num = 3
    for (let i in num){
     console.log(i)
    }
    
    /// RESULT:
    // undefined
    
  2. It supports all kinds of control flow in the loop body, like continue, break, yield and await

  3. It's faster than forEach (stats will be added here :) ).

β€”--

forEach

Now, there's this syntactic sugar: forEach, which takes up to three arguments:

arr.forEach(callback(currentElement, index, array) {
  // do something <- this is a callback
});
Enter fullscreen mode Exit fullscreen mode

which are:

  • currentElement - the element that's being acted on (this one is the only mandatory one),
  • index - the current index of the element,
  • array - the array that's being iterated over

So, a benefit to forEach is that you do have an access to the index, which for... of does not give you (well, there is a lengthy way of obtaining one but it's an overkill). Also, this is a finite loop by definition and so it will end at some point, even if you don't factor in a break statement for an edge-case situation.

There's another, more obscure benefit: the temporary variable (currentElement in the example above) is locally-scoped, whereas it is not so in the for... of loop. See here:


////////////////// forEach

let num = 4;
let arr = [5, 6, 7];

arr.forEach(num => {
  console.log(num);
});

// returns 5, 6, 7

console.log(num)
// 4

////////////////// for... of

for(num of arr){
 console.log(num)
}

// returns 5, 6, 7

console.log(num)
// 7
Enter fullscreen mode Exit fullscreen mode

In the above example, we can see that forEach has not overwritten the value of the num variable declared outside of its scope, whereas that was not the case with for... of loop. However, if you can't name your variables in a more descriptive way, there still is a fix for that: use let inside of the for... of array.

let num = 4;
let arr = [5, 6, 7];

for(let num of arr){
 console.log(num)
}

// returns 5, 6, 7

console.log(num)
// 4
Enter fullscreen mode Exit fullscreen mode

for loop

This loop is just the oldest and most-widely supported for loop in JS. You can always rely on it. It also allows you to be more intentional about which is the starting/ending element, or which direction the iteration should go.

However, the dev experience is not perfect because its syntax is not straightforward so feel free to use for... in (when dealing with objects) or for... of (when dealing with arrays, strings, sets, nodeLists and maps) instead where appropriate.


Photo by Stas Knop from Pexels

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .