JavaScript iterators were introduced in ES6 and they are used to loop over
a sequence of values, usually some sort of collection. By definition, an
iterator must implement a next()
function, that returns an
object in the form of { value, done }
where
value
is the next value in the iteration sequence and
done
is a boolean determining if the sequence has already
been consumed.
A very simple iterator with practical use in a real-world project could be as follows:
class LinkedList {
constructor(data) {
this.data = data;
}
firstItem() {
return this.data.find(i => i.head);
}
findById(id) {
return this.data.find(i => i.id === id);
}
Symbol.iterator]() {
[let item = { next: this.firstItem().id };
return {
next: () => {
= this.findById(item.next);
item if (item) {
return { value: item.value, done: false };
}return { value: undefined, done: true };
,
};
}
}
}
const myList = new LinkedList([
id: 'a10', value: 'First', next: 'a13', head: true },
{ id: 'a11', value: 'Last', next: null, head: false },
{ id: 'a12', value: 'Third', next: 'a11', head: false },
{ id: 'a13', value: 'Second', next: 'a12', head: false },
{ ;
])
for (let item of myList) {
console.log(item); // 'First', 'Second', 'Third', 'Last'
}
In the above example, we implement a
LinkedList
data structure, that internally uses a data
array. Each item in it has a
value
and some implementation-specific properties used to
determine its position in the sequence. Objects constructed from this
class are not iterable by default. To define an iterator we use
Symbol.iterator
and set it up so that the returned sequence
is in order based on the internal implementation of the class, while the
returned items only return their value
.
On a related note, iterators are just functions, meaning they can be
called like any other function (e.g. to delegate the iteration to an
existing iterator), while also not being restricted to the
Symbol.iterator
name. This allows us to define multiple
iterators for the same object. Here’s an example of these concepts at
play:
class SpecialList {
constructor(data) {
this.data = data;
}
Symbol.iterator]() {
[return this.data[Symbol.iterator]();
}
values() {
return this.data
.filter(i => i.complete)
.map(i => i.value)
Symbol.iterator]();
[
}
}
const myList = new SpecialList([
complete: true, value: 'Lorem ipsum' },
{ complete: true, value: 'dolor sit amet' },
{ complete: false },
{ complete: true, value: 'adipiscing elit' },
{ ;
])
for (let item of myList) {
console.log(item); // The exact data passed to the SpecialList constructor above
}
for (let item of myList.values()) {
console.log(item); // 'Lorem ipsum', 'dolor sit amet', 'adipiscing elit'
}
In this example, we use the native array iterator of the
data
object to make our SpecialList
iterable,
returning the exact values of the data
array. Meanwhile, we
also define a values
method, which is an iterator itself,
using Array.prototype.filter()
and
Array.prototype.map()
on the data
array.
Finally, we return the Symbol.iterator
of the result,
allowing iteration only over non-empty objects in the sequence and
returning just the value
for each one.