JavaScript Iterables

Chapter 13 16 mins

Learning outcomes:

  1. What are iterables
  2. Making given objects iterable
  3. Iterability and the spread operator

Introduction

Beginning from where we left in the previous chapter on iterator objects, in this chapter we shall extend all those ideas and discussions by understanding what are iterables.

In particular, we'll learn what defines an iterable, what is the iterable protocol and how are iterators and iterables linked together amidst the @@iterator() method.

So before you begin, just make sure you are well versed with the concept of iterators - otherwise you won't find anything in this chapter meaningful. Assuming that you're all good to go, let's begin!

What are iterables?

We'll start by understanding what is an iterable.

In layman terms:

An iterable is anything that can be iterated over.

Can you think of something that can be iterated over? Well, we've got arrays, strings, sets and many more such structures that can be iterated over.

Although this definition does its job of explaining the essence of an iterable, it doesn't truly convey its underlying implementation. In other words, it doesn't define any rigorous way of declaring something as an iterable.

Even things that can't be iterated over, can be made iterable which goes right against the definition given above.

So let's understand exactly what is an iterable:

Any object that conforms to the iterable protocol is considered to be an iterable.

And you might think: 'Oh come on - first we had an iterator protocol and now we have another one... Iterations sure got some protocol!'

The iterable protocol states that the object must have an @@iterator() method defined which returns an iterator, conforming to the iterator protocol, that we learn in the previous JavaScript Iterators chapters.

Note that this is the same @@iterator() method we saw in the previous chapter.

As a side note, the method has to be just available on the object - this means that it can be either defined directly on the object or defined on at least one of its prototypes. In either case, it will be available on the calling object.

So to boil it down, any object in JavaScript that has an @@iterator() method is, in effect, an iterable.

And there is absolutely no rocket science involved in understanding this.

If an object does have an @@iterator() method defined, the interpreter can call it as when using a for...of loop, and thereby iterate over the object using the returned iterator. Hence the object is iterable.

Similarly, if an object does NOT have an @@iterator() method defined, the interpreter can't call it (in fact this would throw an error) as when using a for...of loop, to retrieve an iterator and thereby can't iterate over the object. Hence the object is not iterable.

Construct a function isIterable() that takes in an object and returns a Boolean value depending on whether or not the object is iterable.

You just need to know how to check for a property on a given object. That's it!

function isIterable(o) {
   return Symbol.iterator in Object(o);
}

The expression Object(o) is given to convert primitive types such as numbers, strings etc. into their respective wrapper object so that the in operator can be validly used on them.

If we only write o, using in on these values would throw an error.

Making iterable

Based on all the discussion above, try to appreciate the fact that an iterable doesn't always have to be an iterable seemingly - we can even get non-iterables to convert to iterables.

And you already know the way we can do so..

For example, numbers are non-iterable by default. However, one can define an @@iterator() method on Number.prototype that returns their corresponding string version's iterator, and thus makes them iterable.

We'll show this example in a moment.

So let's go on and give the iterability characteristic to some non-iterable objects in JavaScript, starting with numbers.

Consider the code below. Try your level best to understand what's happening here and exactly how:

Number.prototype[Symbol.iterator] = function() {
   return this.toString()[Symbol.iterator]();
}
For more info on what's Number.prototype please read JavaScript Object Prototypes.

Tne idea is that when we use the number in a for...of loop, the loop iterates over its individual digits.

This is made possible by first converting the number into a string and then returning the iterator defined on the string.

With the code above in place, can you guess what will the following code log:

var num = 1566;

for (var n of num) {
   console.log(n);
}

Probably, no guesses are required!

1
5
6
6

But here's the real guess...

In the code above, if we replace console.log(n) with console.log(typeof n), what will be logged in the console.

  • Four "string" strings
  • Four "number" strings

So wasn't 'making iterable' an easy task?

As another example, consider the problem of not being able to iterate over an object, like the one shown below:

var o = {x: 10, y: 20, z: 30}

The reason why we can't do so out of the box is apparent - the interpreter can't guess which part of each property do we want i.e do we want the key or do we want the value.

Isn't that quite a lot to guess?

But fortunately, we can tell the interpreter, ourselves, what exactly do we want using the static keys() and values() methods defined on Object.

values() returns an array containing all the individual values in the given object. This excludes symbolic values.

keys() returns an array containing all the individual keys in the given object. This excludes symbolic keys.

And since what we get are arrays, we can right away use these methods in a for...of loop to iterate over the object's values or keys; whichever we want.

Recall that arrays are iterable.

Consider the following examples.

Here we iterate over all the values in the object o:

var o = {x: 10, y: 20, z: 30}

for (var v of Object.values(o)) {
   console.log(v);
}
10
20
30

And here we iterate over all the keys in the object o:

var o = {x: 10, y: 20, z: 30}

for (var k of Object.keys(o)) {
   console.log(k);
}
x
y
z

With both these methods in mind, try solving the tasks below; if you are able to solve 'em then you are damn good to go forward!

Configure Object.prototype such that the following code:

var o = {x: 10, y: 20, z: 30}

for (var v of o) {
   console.log(v);
}

gives the following output:

10
20
30

What we have to do here is just define an @@iterator() method on Object.prototype that returns the iterator of the array returned by Object.values().

Object.prototype[Symbol.iterator] = function() {
   return Object.values(this)[Symbol.iterator]();
}

Configure Object.prototype such that the following code:

var o = {x: 10, y: 20, z: 30}

for (var k of o) {
   console.log(o[k]);
}

gives the following output:

10
20
30

What we have to do here is just define an @@iterator() method on Object.prototype that returns the iterator of the array returned by Object.keys().

Object.prototype[Symbol.iterator] = function() {
   return Object.keys(this)[Symbol.iterator]();
}

The spread operator

Apart from the for...of loop, another place where iterables showcase their skills is the spread operator ....

To recap it, the spread operator takes an sequence and converts it into individual elements; as in the argument of a function or in the element of an array.

Now that you know what are iterables, it's better to say that the spread operator takes an iterable and converts it into individual elements.

The way it does this is that:

  1. First it calls the @@iterator() method defined on the iterable and gets an iterator out of it.
  2. After this, it uses the iterator to retrieve the elements of the iterable one-by-one uptil the point the done property remains false.
The spread operator works exactly how for...of does. It's just that the loop puts each iterated value inside a variable, whereas the spread operator puts each value in another sequence.

Shown below is an illustration:

var arr = [3, 4, 5];
var arr2 = [1, 2, ...arr, 6];

console.log(arr2); // [1, 2, 3, 4, 5, 6]

The ...arr expression here performs the following steps:

  1. First, it calls the @@iterator() method on the arr object. This returns an iterator object that allows iteration over arr.
  2. Then, this iterator is consumed by calling next(), uptil the point the done property stays false.
  3. With each iteration, the value property is pushed onto arr2.

When the iteration discussed above is done the final array constructed uptil that point is [1, 2, 3, 4, 5]. Once the last element 6 is added to arr2, the array becomes as shown in the comment above.

Another easy, but fairly useful use of iterables.

Moving on...

Modern iteration in JavaScript lies on the pillar of iterators and iterables.

Iterators were covered in enormous detail in the previous chapter, whereas iterables were covered in this very chapter. If you feel you've missed on any concept or idea within both these topics, consider a refresher. Refreshers do help!

We can use iterators to iterate over given objects that are meant to be iterable. Iterators are simply objects providing iteration utilities, while iterables are objects allowing iteration over themselves.

Both concepts are entirely different, but largely related!

But have you ever thought of combining both these concepts into one, single object? If you haven't, then the next chapter on JavaScript Generators will surely get you to do so.