In JavaScript, iterables are objects that allow us to traverse and access the elements of a collection of data sequentially and easily.
In simple terms, iterables and iterators are a tool that allows us to “iterate” over the values of a collection.
Unlike traditional methods of direct access to elements (like indices in arrays), iterators allow traversing collections without needing direct access to the internal structure.
Iterables can be traversed sequentially using a for...of
loop or other functions that work with iterables, such as map()
, filter()
, reduce()
, among others.
How iterables work
Under the hood, an iterable is any object that implements the Symbol.iterator
method, which returns an iterator.
On the other hand, an iterator is simply an object that implements a next()
method. This method returns an object with two properties:
value
: The current value of the iteration.done
: A boolean value (true
orfalse
) that indicates whether the iteration has finished.
For example, when using an iterator to traverse an array, each call to next()
will return the next value of the array until the end is reached.
In summary,
- Iterable: An object that has a
Symbol.iterator
method that returns an iterator. - Iterator: An object with the
next()
method that returns an object with thevalue
anddone
properties.
Using iterators with loops
Manual iteration with next()
To use an iterator, we can call it manually using the next()
method.
const iterable = [10, 20, 30];
const iterator = iterable[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
console.log(result.value); // Prints the values 10, 20, 30
result = iterator.next();
}
In this example,
- We start the iteration with
iterator.next()
- Then we use a
while
loop to continue iterating until thedone
property is marked astrue
.
Using for...of
A simpler and more common way to work with iterators is through the for...of
loop, which automatically handles the iteration process.
const iterable = [1, 2, 3, 4];
for (const value of iterable) {
console.log(value); // Prints 1, 2, 3, 4
}
- This loop will take care of calling
next()
repeatedly until the entire iterable has been traversed - Internally, the loop uses the
next()
method of the iterator.
Creating our own iterators and iterables
We can also create our own iterables and iterators. This is useful when we need a custom way to iterate over a collection of data that is not standard, or when we are working with complex data structures.
Custom iterator
For example, imagine we want to create an iterator to traverse an object that contains a list of values. We can do this by implementing the next()
method within an object:
const myIterator = {
values: [10, 20, 30],
currentIndex: 0,
next() {
if (this.currentIndex < this.values.length) {
return { value: this.values[this.currentIndex++], done: false };
}
return { value: undefined, done: true };
}
};
console.log(myIterator.next()); // { value: 10, done: false }
console.log(myIterator.next()); // { value: 20, done: false }
console.log(myIterator.next()); // { value: 30, done: false }
console.log(myIterator.next()); // { value: undefined, done: true }
In this example,
- We have created an object
myIterator
that acts as an iterator for an array of numbers. - The
currentIndex
property keeps track of the current index in the array - The
next()
method returns the next value in the sequence until the end is reached.
Custom iterable
To create an iterable object, we must implement the Symbol.iterator
method, which is a type of symbol property in JavaScript.
class CustomCollection {
constructor(...items) {
this.items = items;
}
[Symbol.iterator]() {
let index = 0;
const items = this.items;
return {
next() {
if (index < items.length) {
return { value: items[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
}
const collection = new CustomCollection('x', 'y', 'z');
for (const item of collection) {
console.log(item);
}
// Output:
// x
// y
// z
In this example,
CustomCollection
is an object that implements theSymbol.iterator
method- This returns an iterator with the
next()
method that returns the values of theitems
property one by one until the end of the sequence is reached.
Using generators as iterables
We can also use generators to create iterable objects in a more concise and readable way.
function* myGenerator() {
yield 'Hello';
yield 'World';
}
const iterator = myGenerator();
for (const value of iterator) {
console.log(value);
}
In this example, the generator myGenerator
creates an iterable object that yields the values “Hello” and “World” when traversed with a for...of
loop.
If you want to learn more about generator functions, check out the entry