While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
JavaScript supports a protocol by which objects such as arrays can be used by control structures such as for…of and the spread operator ...
to loop through data sequentially. This is referred to as the iterable and the data structures that support this functionality are called iterables. While JavaScript provides maps, arrays and sets with an iterable property from the get-go, plain objects do not have this by default.
Iterables are data structures which provide a mechanism to allow other data consumers to publicly access its elements in a sequential manner. Imagine a self-packaged data structure that unloads data one-by-one in order when put inside of a for...of
loop.
The concept of the iterable protocol can be split into the iterable (the data structure itself) and the iterator (sort of a pointer that moves over the iterable). Let’s consider an array for example, when the array is used in a for...of
loop, the iterable property is called which returns an iterator
. This iterable property is namespaced as Symbol.iterator
and the object that it returns can be used on a common interface that is shared by all looping control structures.
In a way, the Symbol.iterator
can be compared to a iterator factory that produces an iterator whenever the data structure is placed in a loop.
As an iterator moves over the data structure and provides the elements sequentially, the object returned by the iterable contains a value
and a done
property.
The value indicates the current data value pointed by the iterator and done
is a boolean that tells us if the iterator has reached the last element in the data structure.
This {value, done}
is consumed by structures such as loops. So how does the iterator method call the next object? Using a next()
method that’s defined within the Symbol.iterator() method.
A better definition for the iterator property that I can come with at this point is that it’s an property that knows how to access elements from a collection one by one and also provides a logical rule to stop doing so (eg. if there are no more elements in the array).
JavaScript objects are cool and all, but why don’t they have iterables? Well, some of the reasons could be:
[Symbol.iterator]()
into the object would make for a nasty surprise.for...in
loop.All the points above except the last one (I hate to admit that I’m too comfy with regular objects to move to maps) are good reasons not to have iterables in objects, but what if your boss wanted your JavaScript objects to have one?
A simple iterable implementation on objects would look like this:
let Reptiles = {
biomes: {
water: ["Alligators", "Crocs"],
land: ["Snakes", "Turtles"]
},
[Symbol.iterator]() {
let reptilesByBiome = Object.values(this.biomes);
let reptileIndex = 0;
let biomeIndex = 0;
return {
next() {
if (reptileIndex >= reptilesByBiome[biomeIndex].length) {
biomeIndex++;
reptileIndex = 0;
}
if (biomeIndex >= reptilesByBiome.length) {
return { value: undefined, done: true };
}
return {
value: reptilesByBiome[biomeIndex][reptileIndex++],
done: false
};
}
};
}
};
// let's now iterate over our new `Reptiles` iterable:
for (let reptile of Reptiles) console.log(reptile);
The output would be:
Alligators
Crocs
Snakes
Turtles
With this example, we see iterators can be implemented within the object. Iterables can be powerful properties for objects that provide ease of use while handling certain situations and help us avoid writing long path names.
Loops like for...of
have a built-in mechanism to consume iterables until the done
value evaluates to true. What if you want to consume the iterable on your own though, without a built-in loop? Simple, you get the iterator from the iterable and then call next() on it manually.
Given the same example as above, we could get an iterator from Reptiles
by calling its Symbol.iterator
like this:
let reptileIterator = Reptiles[Symbol.iterator]();
You can then use the iterator like this:
console.log(reptileIterator.next());
// {value: "Alligators", done: false}
console.log(reptileIterator.next());
// {value: "Crocs", done: false}
console.log(reptileIterator.next());
// {value: "Snakes", done: false}
console.log(reptileIterator.next());
// {value: "Turtles", done: false}
console.log(reptileIterator.next());
// {value: undefined, done: true}
console.log(reptileIterator.next());
// TypeError: Cannot read property 'length' of undefined
As you can see, the iterator has a next()
method that returns the next value in the iterable. The value for done
only evaluates to true
after another next()
call once the last value has been returned, so to go over the entire iterable there will always be one more call to next()
than there is data in the iterable. Calling next()
again after an iterator has reached the end of the itarable will result in a TypeError
being thrown.
I hope that this introduction was eye opening in understanding a little more about JavaScript’s internals for data structures such as objects. This only scratched the surface and, if you want to learn more, I invite you to read Kyle Simpson’s excellent chapter on Iterables.
Join our DigitalOcean community of over a million developers for free! Get help and share knowledge in our Questions & Answers section, find tutorials and tools that will help you grow as a developer and scale your project or business, and subscribe to topics of interest.
Sign up