JavaScript supports looping through data set objects such as arrays using control structures such as for…of and the spread operator ...
. 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 by default, regular JavaScript 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, a pointer that moves over the iterable. 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 an 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. In order for the iterator method to call the next object, a next()
method that’s defined within the Symbol.iterator() method.
In other words, the iterator property can be defined as a property that knows how to access elements from a collection one by one. It also contains the logic to stop, such as when there are no more elements in an array.
JavaScript objects don’t come with iterables by default. Here are some potential rationales::
[Symbol.iterator]()
into the object could result in unexpected behavior.for...in
loop.If you still need an iterable, 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
};
}
};
}
};
// now iterate over the new `Reptiles` iterable:
for (let reptile of Reptiles) console.log(reptile);
The output would be:
Alligators
Crocs
Snakes
Turtles
With this example, you can 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. If you want to consume the iterable on your own though, without a built-in loop, you can get the iterator from the iterable and then call next() on it manually.
Given the same example as above, you 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.
With this tutorial, you have gone through the different parts and mechanisms behind looping through a data set with JavaScript. You’ve gone through the reasons JavaScript objects do not come with this ability to iterate innately, and demonstrated the option to manually implement your own iterables.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!