Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / ES5

Understanding Loops Drawbacks and Iteration Protocols in JavaScript

5.00/5 (4 votes)
22 Aug 2020CPOL5 min read 6.7K  
Learning the drawbacks of different loop constructs and learn how to use the different iteration protocols
In this article, by the time you have finished it, you'll able to understand the concepts of iterators and its benefits. Not only that, knowing the different drawbacks of different loops gives you the power to choose what loop and when to use it in a given scenario.

Introduction

If you have been programming with JavaScript or any language for a while now, for-loops shouldn’t be alien to you. Haven’t you noticed that many programming-languages, including JavaScript, have shifted from iterating data using for-loops to using iterator-objects that return the next item of a given collection. Moreover, iterators made our lives easier and productive when working with collections. That’s why understanding JavaScript iterators are beneficial to every developer and will explore this subject in this article. OK, then let’s get started.

Background

Before you begin, it is advisable to read the JavaScript Symbols, you can check out my post here: Basics of JavaScript Symbols. One reason is that we may come across the usage of Symbol.iterator. Just a note, JavaScript iterators won’t be replacing for-loops and/or other loop constructs. However, iterators' main goal is to eliminate the complexity and error-prone nature of loops.

Table of Contents

The Different Loops Construct Drawbacks

For-loop

For-loops are one of the quintessential in learning any programming language including JavaScript. In general working with arrays, you’ll typically use a for-loop to iterate over the data elements.

Let's see an example below:

JavaScript
/*simple loop*/
let dogsName = ['Tank', 'Diesel', 'Cooper', 'Bruno', 'Thor'];

for (let index = 0; index < dogsName.length; index++) {
  console.log(dogsName[index]);
}
/*end of simple loop*/

I know, the code sample is quite straightforward. How about creating a complex one?

JavaScript
/*complex loop*/
let x = 100;

for (let i = 0; i <= x; i++) {
  for (let j = 1; (j + x / 2) < x; j++) {
    for (let k = 1; k <= x; k++) {
      console.log("hello");
    }
  }
}
/*end of complex loop*/

Complexity grows, when nesting loops which can result in tracking multiple variables inside the loops. Thus, this can make your loop error-prone.

Array.forEach

However, why use for-loop if you are just going to iterate over the data items, right? Array.forEach to the rescue, not quite. Why? Before we answer that, let's see an example first.

JavaScript
/*forEach loop*/
let amounts = [1.25, 2.25, 3.25, 4.25];

/**
 * Outputs: 
 * 1.25
 * 2.25
 * 3.25
 * 4.25
 */
amounts.forEach((item) => {
    console.log(item);
});
/*end of forEach loop*/

Now, going back to the question. Because using the Array.forEach loop doesn't give you the ability to break within the loop construct.

Let's see an example below:

JavaScript
/*forEach loop*/
let amounts = [1.25, 2.25, 3.25, 4.25];
amounts.forEach((item) => {

  if (item === 3.25) {
    break; //throws an error: "Uncaught SyntaxError: Illegal break statement"
  }
}); 
/*end of forEach loop*/ 

For-in loop

How about when you are interested only in getting those properties of an object? The for-in loop comes to the rescue.

Let's see an example below.

JavaScript
/** Start of for-in loop */
let customer = { firstName: "Jin Vincent", lastName: "Necesario", country: "Philippines" };

/**
 * Output:
 * firstName
 * lastName
 * country
 */
for (let key in customer) {
    console.log(key);
}

/** End of for-in loop */

However, the for-in loop can only iterate over enumerable, non-Symbol properties. Thus, it doesn't make any sense to iterate over an Array where the index order is significant.

Let's see an example below.

JavaScript
/** Start of for-in loop that iterates over an Array */
/** Warning: Please don't do this on your project/product! */

Array.prototype.bar = 1;

let products = [1001, "mouse", "monitor", { firstName: "Jin Vincent" }];

/**Output:
 * 0
 * 1
 * 2
 * 3
 * bar
 */

for (let prop in products) {
    console.log(prop); //this outputs the index of the array and bar
}
/** End of for-in loop that iterates over an Array*/

For-of loop

If you want to iterate over an array or a collection and you don't mind the index, for-of loop saves the day.

You can use the for-of loop with objects or collections that implement the iterable protocol.

More on the iterable protocol in the latter part of the article.

Let's see an example below.

JavaScript
/** Start of for of loop */
const xmenGoldTeam = ['Storm', 'Bishop', 'Colossus', 'Jean Grey', 'Iceman', 'Archangel'];

/**Outputs: 
 * Storm
 * Bishop
 * Colossus
 * Jean Grey
 * Iceman
 * Archangel
 */
for (let xmen of xmenGoldTeam) {
    console.log(xmen);
}
/** end of for of loop */

When an object isn't iterable, therefore, you can't use the for-of loop to iterate over the properties.

Let's see an example below:

JavaScript
/** Start of for-of loop */
let customers = { firstName: "Jin Vincent", lastName: "Necesario", country: "Philippines" };

for (const customer of customers) {
    //TypeError: customers is not iterable
}
/** End of for-of loop */  

Iteration Protocol

Basically, the iteration protocol is a set of rules that an object needs to follow when implementing an interface. In other words, when using a certain protocol (it could be iterable or iterator), your object should implement the following functionality and with some kind of rules.

Types of Iteration Protocol

  • Iterator protocol
  • Iterable protocol

What Is an Iterator Protocol?

Implementing the iterator protocol, you should follow the following conventions:

  • An object should implement .next() method.
  • The .next() method must return an object that contains the following properties: value and done.

In short, an object is an iterator when it provides a next() method that returns the next item in the sequence of a group of items that has two properties.

These two properties are done and value.

  • done - a boolean type that indicates whether or not there are any existing elements that could be iterated upon
  • value - the current element

Let's see an example below and follow the conventions.

JavaScript
/*Start of an iterator protocol*/

const countries = {
    collection: ["Philippines", "Singapore", "Malaysia", "Canada", "Brazil", "Australia"],
    index: 0,
    next: function () {
        if (this.index < this.collection.length) {
            return {
                value: this.collection[this.index++],
                done: false
            }
        } else {
            return {
                done:true
            }
        }
    }
}

console.log(countries.next()); //output: {value: "Philippines", done: false}
console.log(countries.next()); //output: {value: "Singapore", done: false}
console.log(countries.next()); //output: {value: "Malaysia", done: false}
console.log(countries.next()); //output: {value: "Canada", done: false}
console.log(countries.next()); //output: {value: "Brazil", done: false}
console.log(countries.next()); //output: {value: "Australia", done: false}
console.log(countries.next()); //output: {done: true}

/*End of an iterator protocol*/

What is an Iterable Protocol?

Before starting, one thing to point out, JavaScript does have well-known symbols which are usually prefixed using the @@notation. Like the Symbol.iterator, the symbol is referred to as the @@iterator.

Now going back to iterable protocol. When an object implements the @@iterator method are considered iterable. In addition, this method returns the iterator. The object's property @@iterator key which is available via Symbol.iterator.

Let's see an example to understand the statement above.

JavaScript
/*Start of an iterable protocol*/

const countries = {
    countryCollection: 
    ["Philippines", "Singapore", "Malaysia", "Canada", "Brazil", "Australia"],
    startIndex: 0,
    [Symbol.iterator]: function () {
        return {
            collection: this.countryCollection,
            index: this.startIndex,
            next: function () {
                if (this.index < this.collection.length) {
                    return {
                        value: this.collection[this.index++],
                        done: false
                    }
                } else {
                    return {
                        done: true
                    }
                }
            }
        }
    }
}

let countryIterator = countries[Symbol.iterator]();

console.log(countryIterator.next()); //output: {value: "Philippines", done: false}
console.log(countryIterator.next()); //output: {value: "Singapore", done: false}
console.log(countryIterator.next()); //output: {value: "Malaysia", done: false}
console.log(countryIterator.next()); //output: {value: "Canada", done: false}
console.log(countryIterator.next()); //output: {value: "Brazil", done: false}
console.log(countryIterator.next()); //output: {value: "Australia", done: false}
console.log(countryIterator.next()); //output: {done: true}

/**
 * Outputs: 
 * Philippines
 * Singapore
 * Malaysia
 * Canada
 * Brazil
 * Australia
 * undefined
 */
for (country of countries) {
    console.log(country)
}

/*End of an iterable protocol*/

Now that we have an idea of what is an iterator and iterable protocol are and where the for-of loop comes into play. Why not check and learn the iterable built-in types?

Iterator Built-in Types

The JavaScript language has built-in iterable types but we won't cover everything but we are going to cover String, Array, and Set.

For the complete list of the iterable built-in types, here they are:

  • String
  • Array
  • Map
  • Set
  • TypedArray
  • Arguments Object

Check if Object/type Implements Symbol.iterator

JavaScript
/** Start of how to check if a type/object implements the Symbol.iterator */

let programmingLanguage = "JavaScript";

//let's us check if the built-in data-type String implements Symbol.iterator
//output: [Symbol(Symbol.iterator)]
Object.getOwnPropertySymbols(programmingLanguage.__proto__);

/** end of how to check if type/object implements the Symbol.iterator */

As you can see, the code sample above shows that the String data-type does implement the @@iterator. Thus, Strings are iterable.

How Does a Built-in Iterator Look Like?

Let's go straight to an example to see how does an iterator looks like.

JavaScript
//declare a JavaScript string type
let programmingLanguage = "JavaScript";

//lets invoke the method having an @@iterator property key
let iterator = programmingLanguage[Symbol.iterator]();

//let's check if the @@iterator property function return an iterator
console.log(iterator); //output: StringIterator

As you can see, with the example above, we have declared a String type and invokes the @@iterator key symbol and checks whether the function really returns an iterator and it does return a StringIterator.

Quick Examples of Iterator Built-in Types

We won't be going into detail because we have already discussed the concepts above. However; the objective here is to show how we can interact with the built-in iterators.

Array Iterator

The @@iterator method is part of the iterable protocol, that defines how to iterate over the items.

JavaScript
const pets = ["Andy", "Barkley", "Zandro"];
const petIterator = pets[Symbol.iterator]();
console.log(...petIterator); //outputs: Andy Barkley Zandro

String Iterator

The [@@iterator]() method returns an iterator that iterates the String values.

JavaScript
const fullName = "Jin Vincent Necesario";
const fullNameIterator = fullName[Symbol.iterator]();
console.log(...fullNameIterator); //outputs: J i n   V i n c e n t   N e c e s a r i o

Set Iterator

Set.prototype[@@iterator]()

The initial value of the @@iterator is the same as the initial value of the values property.

JavaScript
const myArraySet = new Set(['mango', 'avocado', 'apple', 'apple', 'mango']);
const myArraySetIterator = myArraySet[Symbol.iterator]();

//to show that the initial value of the @@iterator is the same as the initial values
console.log(myArraySet.values()); //output: SetIterator {"mango", "avocado", "apple"}
console.log(myArraySetIterator);  //output: SetIterator {"mango", "avocado", "apple"}

console.log(...myArraySetIterator); //outputs: mango avocado apple 

Summary

In this post, we have seen the strength and drawbacks of the different JavaScript loops. Not only that, but we have also tackled the iteration protocols, which basically has two types which are iterator and iterable protocol. Lastly, we have seen how built-in iterators work. Overall, this post has walked you through the puzzle of iterators.

I hope you have enjoyed this article, as I have enjoyed writing it. Stay tuned for more. Until next time, happy programming!

History

  • 22nd August, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)