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:
let dogsName = ['Tank', 'Diesel', 'Cooper', 'Bruno', 'Thor'];
for (let index = 0; index < dogsName.length; index++) {
console.log(dogsName[index]);
}
I know, the code sample is quite straightforward. How about creating a complex one?
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");
}
}
}
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.
let amounts = [1.25, 2.25, 3.25, 4.25];
amounts.forEach((item) => {
console.log(item);
});
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:
let amounts = [1.25, 2.25, 3.25, 4.25];
amounts.forEach((item) => {
if (item === 3.25) {
break;
}
});
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.
let customer = { firstName: "Jin Vincent", lastName: "Necesario", country: "Philippines" };
for (let key in customer) {
console.log(key);
}
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.
Array.prototype.bar = 1;
let products = [1001, "mouse", "monitor", { firstName: "Jin Vincent" }];
for (let prop in products) {
console.log(prop);
}
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.
const xmenGoldTeam = ['Storm', 'Bishop', 'Colossus', 'Jean Grey', 'Iceman', 'Archangel'];
for (let xmen of xmenGoldTeam) {
console.log(xmen);
}
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:
let customers = { firstName: "Jin Vincent", lastName: "Necesario", country: "Philippines" };
for (const customer of customers) {
}
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.
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());
console.log(countries.next());
console.log(countries.next());
console.log(countries.next());
console.log(countries.next());
console.log(countries.next());
console.log(countries.next());
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.
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());
console.log(countryIterator.next());
console.log(countryIterator.next());
console.log(countryIterator.next());
console.log(countryIterator.next());
console.log(countryIterator.next());
console.log(countryIterator.next());
for (country of countries) {
console.log(country)
}
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
let programmingLanguage = "JavaScript";
Object.getOwnPropertySymbols(programmingLanguage.__proto__);
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.
let programmingLanguage = "JavaScript";
let iterator = programmingLanguage[Symbol.iterator]();
console.log(iterator);
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.
const pets = ["Andy", "Barkley", "Zandro"];
const petIterator = pets[Symbol.iterator]();
console.log(...petIterator);
String Iterator
The [@@iterator]()
method returns an iterator that iterates the String
values.
const fullName = "Jin Vincent Necesario";
const fullNameIterator = fullName[Symbol.iterator]();
console.log(...fullNameIterator);
Set Iterator
Set.prototype[@@iterator]()
The initial value of the @@iterator
is the same as the initial value of the values
property.
const myArraySet = new Set(['mango', 'avocado', 'apple', 'apple', 'mango']);
const myArraySetIterator = myArraySet[Symbol.iterator]();
console.log(myArraySet.values());
console.log(myArraySetIterator);
console.log(...myArraySetIterator);
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