Mariam Reba Alexander

What you may not know about loops in JS - part 2 - for/of , for/in

Created July 6, 2021

In the last post part 1 - for loop, I described how for loop works and shared some common and rare examples of it.

Now let's look into the other types of loop: for/of and for/in loops.

for/of

The for/of loop is fairly new defined by ES6 and is completely different from for loop.

This loop can be used for iterable objects (eg. arrays, strings, sets and maps). Let's go through each of the types.

with arrays

let numbers = [1, 2, 3, 4], sum = 0;
for(let el of numbers) {
    sum += el;
}
sum       // => 10

In the above code, the loop iterates through each element of the numbers array. Different from the initialize expression of the for loop, here you can see that the variable el is assigned with the the next element of the numbers array, before each loop execution.

Using this concept let's create another infinite loop 🤓.

let numbers = [1, 2, 3, 4], sum = 0;
for(let el of numbers) {
    sum += el;
    numbers.push(sum);
}
console.log(sum);
// => iteration can never reach the end of the element, and loops infinitely. 

never ending gif

with objects

Wait a second, but objects are not iterable. Yes, you are right and if you attempt to loop on an object , the following error will be thrown.TypeError because o is not iterable.

You can loop through objects with for/of loop using either of these methods, Object.keys(), Object.values()or Object.entries(). These are iterable because they return an array of properties.

//Object.keys returns an array of property names
let o = { x: 1, y: 2, z: 3 };
let keys = "";
for(let k of Object.keys(o)) {
    keys += k;
}
keys  // => "xyz"

//Object.values returns an array of property values.
let sum = 0;
for(let v of Object.values(o)) {
    sum += v;
}
sum // => 6

//Object.entries returns an array of keys and values.
let pairs = "";
for(let [k, v] of Object.entries(o)) {
    pairs += k + v;
}
pairs  // => "x1y2z3"

with strings

Strings can be iterated character by character.

let occurrence = {};
for(let char of "Panama") {
    if (occurrence[char]) {
        occurrence[char]++;
    } else {
        occurrence[char] = 1;
    }
}
occurrence   // => {p: 1, a: 3, n: 1, m: 1}

Strings are iterated by by Unicode codepoint, not by UTF-16 character. For example in the below code, even when the string length is 4 ( no. of UTF-16 characters), the loop happens 3 times, i.e by the number of unicode codepoint.

const msg = "❤️🐶";
console.log(msg.length); //outputs => 4
for(let item of msg){
  console.log("wuff"); // logs "wuff" 3 times
}

confused gif

with set and map

With set, the loop body runs once for each element of the set.

let text = "oh no no no no no way!";
let wordSet = new Set(text.split(" "));
let unique = [];
for(let word of wordSet) {
    unique.push(word);
}
console.log(unique); // => ["oh", "no", "way"]

Interestingly with map, it neither iterates the Map keys nor the map values but the key/value pairs i.e each time the iterator returns an array, where the first element is the key and the second element is the respective value.

let m = new Map([[1, "one"]]);
for(let [key, value] of m) {
    key    // => 1
    value  // => "one"
}

for/in

A for/in loop is a lot like for/of loop except that the keyword is in instead of of. As compared to for/of loop, for/in loop iterates through any object.

for(let p in o) {      // Assign property names of o to variable p
    console.log(o[p]); // Print the value of each property
}

In the above example the variable p will hold the property name of the object each time it loops.

Again the left hand side expression can be any expression than just a variable declaration, like the following example.

let o = { x: 1, y: 2, z: 3 };
let a = [], i = 0;
for(a[i++] in o) /* empty */; //empty body statement 
console.log(a); // => ["x", "y", "z"]

Since the left hand expression is evaluated each time during iteration, you can use this behaviour to code like above to copy all the object properties into an array 😎.

Note:

let o = { x: 1 };
o.propertyIsEnumerable("x")  // => true: o has an own enumerable property x
o.propertyIsEnumerable("toString")  // => false: not an own property
Object.prototype.propertyIsEnumerable("toString") // => false: not enumerable

Hope you got enlightened by these posts and will in future choose wisely between for/in and for/of loop in your code.


Mariam Reba Alexander