Easily Iterate Over JavaScript Collections with the For-Of Loop

John Au-Yeung
Nov 12 · 7 min read
Photo by Tine Ivanič on Unsplash

Starting with ES2015, we have a new kind of loop to loop over iterable objects. The new for...of loop is a new kind of loop that lets us loop over any iterable objects without using a regular for loop, while loop, or using the forEach function in the case of arrays. It can be used directly to iterate through any iterable objects, which include built in objects like Strings, Arrays, array-like objects like arguments and NodeList , TypedArray , Map , Set and any user-defined iterables. User-defined iterables include entities like generators and iterators.

If we want to use the for...of loop to iterate over an iterable object, we can write it with the following syntax:

for (variable of iterable){
// run code
}

The variable in the code above is the variable representing each entry of the iterable object that are being iterated over. It can be declared with const , let or var . The iterable is the object where the properties are being iterated over.

For example, we can use it to iterate over an array like in the following code:

const arr = [1,2,3];

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

The code above, the console.log statements will log 1, 2, and 3. We can replace const with let if we want to assign the variable we used for iteration in the for...of loop. For example, we can write:

const arr = [1,2,3];

for (let num of arr) {
num *= 2 ;
console.log(num);
}

The code above, the console.log statements will log 2, 4, and 6 since we used the let keyword to declare the num so we can modify num in place by multiplying each entry by 2. We cannot reassign with const so we have to use let or var to declare the variable we want to modify in each iteration.

We can also iterate over strings. If we do that we all get each character of the string in each iteration. For example, if we have the code below:

const str = 'string';

for (const char of str) {
console.log(char);
}

Then we get the individual characters of 'string' logged in each line.

Likewise, we can iterate over TypedArrays, which contains binary data represented by a series of numbers in hexadecimal format. For example, we can write the following code:

const arr = new Uint8Array([0x00, 0x2f]);

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

In the example above, console.log will log 0 and 47. Note that the logged value is in decimal format but the entered value is in hexadecimal format.

If we iterate over Maps, then we get each entry of the Map. For example, we can write the following code to iterate over Maps:

const map = new Map([['a', 2], ['b', 4], ['c', 6]]);

for (const entry of map) {
console.log(entry);
}

If we log the entries, we get ['a', 2], ['b', 4], and ['c', 6] . Maps consists of key-value pairs as their entries. When we iterate over a Map, we get the key as the first element and the value as the second element is each entry. To get the key and value of each entry into its own variable we can use the destructuring operator, like in the following code:

const map = new Map([['a', 2], ['b', 4], ['c', 6]]);

for (const [key, value] of map) {
console.log(key, value);
}

Then when we log the entries, we get 'a' 2, 'b' 4, and 'c' 6 .

We can also use the for...of loop for Sets. For example, we can loop over a Set by doing the following:

const set = new Set([1, 1, 2, 3, 3, 4, 5, 5, 6]);

for (const value of set) {
console.log(value);
}

We set that we get 1, 2, 3, 4, 5, and 6 logged since the Set constructor automatically eliminates duplicate entries by keeping the first occurrence a value in the Set and discarding the later occurrence of the same value.

The for...of loop also works for iterating over the arguments object, which is an global object that has the arguments that were passed into the function when the function is called. For example, if we write the following code:

(function() {
for (const argument of arguments) {
console.log(argument);
}
})(1, 2, 3, 4, 5, 6);

We see that we see 1, 2, 3, 4, 5, and 6 logged since this is what we passed in when we called the function. Note that this only works for regular functions since the context of this has to be changed to the function being called instead of window . Arrow functions doesn’t change the content of this , so we won’t get the correct arguments when we run the same loop inside an arrow function.

Also, we can iterate over a list of DOM Node objects, called a NodeList . For example, is a browser implemented the NodeList.prototype[Symbol.iterator] , then we can use the for...of loop like in the following code:

const divs = document.querySelectorAll('div');

for (const div of divs) {
console.log(div);
}

In the code above we logged all the div elements that are in the document.

With for...of loops, we can end the loop by using the break , throw or return statements. The iterator will close in this case, but the execution will continue outside the loop. For example, if we write:

function* foo(){ 
yield 'a';
yield 'b';
yield 'c';
};

for (const o of foo()) {
console.log(o);
break;
}
console.log('finished');

In the code above, we only log ‘a’ because we have a break statement at the end of the for...of loop, so after the first iteration, the iterator will close and the loop ends.

Photo by Charlotte Coneybeer on Unsplash

We can loop over generators, which are special functions that returns a generator function. The generator function returns the next value of an iterable object. It’s used for letting us iterate through a collection of objects by using the generator function in a for...of loop.

We can also loop over a generator that generate infinite values. We can have an infinite loop inside the generator to keep returning new values. Because the yield statement doesn’t run until the next value is requested, we can keep an infinite loop running without crashing the browser. For example, we can write:

function* genNum() {
let index = 0;
while (true) {
yield index += 2;
}
}
const gen = genNum();
for (const num of gen) {
console.log(num);
if (num >= 1000) {
break;
}
}

If we run the code above, we see that we get numbers from 2 to 1000 logged. Then num is bigger than 1000, so that the break statement is ran. We cannot reuse the generator after it’s closed, so if we write something like the following:

function* genNum() {
let index = 0;
while (true) {
yield index += 2;
}
}
const gen = genNum();
for (const num of gen) {
console.log(num);
if (num >= 1000) {
break;
}
}
for (const num of gen) {
console.log(num);
if (num >= 2000) {
break;
}
}

The second loop won’t run because the iterator that was generated by the generator is already closed by the first loop with the break statement.

We can iterate over other iterable objects that have the method denoted with the Symbol.iterator Symbol defined. For example, if we have the following iterable object defined:

const numsIterable = {
[Symbol.iterator]() {
return {
index: 0,
next() {
if (this.index < 10) {
return {
value: this.index++,
done: false
};
}
return {
value: undefined,
done: true
};
}
};
}
};

Then we can run the loop below to show log the generated results:

for (const value of numsIterable) {
console.log(value);
}

When we run it, should see 0 to 9 logged when console.log is run in the loop above.

It’s important that we don’t confuse the for...of loop with the for...in loop. The for...in loop if for iterating over the top-level keys of objects including anything up the prototype chain, while the for...of loop can loop over any iterable object like arrays, Sets, Maps, the arguments object, the NodeList object, and any user-defined iterable objects.

For example, if we have something like:

Object.prototype.objProp = function() {};
Array.prototype.arrProp = function() {};
const arr = [1, 2, 3];
arr.foo = 'abc';
for (const x in arr) {
console.log(x);
}

Then we get 0, 1, 2, ‘foo’, ‘arrProp’ and ‘objProp’ logged, which are keys of objects and methods that are defined for the arr object. It included all properties and methods up the prototype chain. It inherited all properties and methods from Object and Array that were added to Object and Array’s prototype so we get all the things in the chain inheritance in the for...in loop. Only enumerable properties are logged in the arr object in arbitrary order. It logs index and properties we defined in Object and Array like objProp and arrProp .

To only loop through properties that aren’t inheritance from an object’s prototype, we can use the hasOwnPropetty to check if the property is defined on the own object:

Object.prototype.objProp = function() {};
Array.prototype.arrProp = function() {};
const arr = [1, 2, 3];
arr.foo = 'abc';
for (const x in arr) {
if (arr.hasOwnProperty(x)){
console.log(x);
}
}

objProp and arrProp are omitted because they’re they’re inherited from Object and Array objects respectively.

The for...of loop is a new kind of loop that lets us loop over any iterable objects without using a regular for loop, while loop, or using the forEach function in the case of arrays. It can be used directly to iterate through any iterable objects, which include built in objects like Strings, Arrays, array-like objects like arguments and NodeList , TypedArray , Map , Set and any user-defined iterables. User-defined iterables include entities like generators and iterators. This is a handy loop because it lets us over any iterable object rather than just arrays. Now we have a loop statement that works with iterable object.

JavaScript in Plain English

Learn the web's most important programming language.

John Au-Yeung

Written by

Web developer. Subscribe to my email list now at http://jauyeung.net/subscribe/ . Follow me on Twitter at https://twitter.com/AuMayeung

JavaScript in Plain English

Learn the web's most important programming language.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade