Javascript Custom Iterables
A while back I was creating a URLPattern
class that allows you to match a URL
Pattern to a URL. I allowed ', '
to act as an and operator (&&
). In that way,
my class was the following structure:
{
0: {
"pattern": String,
"match": Function
},
1: {
"pattern": String,
"match": Function
},
...
n: {
"pattern": String,
"match": Function
},
length: n + 1
}
I wanted someone to be able to do [...pattern]
and get back:
Array<{ pattern: String; match: Function }>; // length = n + 1
That is when I came across the Symbol.iterator
property.
Symbol.iterator
From the MDN docs:
The well-known
Symbol.iterator
symbol specifies the default iterator for an object. Used byfor...of
.
The Symbol.iterator
is a property of the builtin Symbol
object that exists
as a property on the Array
prototype, Set
prototype, and many other builtin iterators.
It gets called whenever the ...
(spread syntax) or Array.from
is run on the
object or for...of
.
Implementing in class
To use Symbol.iterator
inside classes, define a method with the method name being
[Symbol.iterator]
. Here is an example usage:
class A {
[Symbol.iterator]() {}
}
An example that will give back [1, 2, 3]
is shown below:
class A {
constructor() {
this.isNothing = true;
}
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
Now the following results can occur:
const value = new A();
[...A]; // [1, 2, 3]
Array-like iterator
If you want an iterator that would do the following:
[...new myClass({ 0: 1, 1: 2, 2: 3, length: 3 })]; // [1, 2, 3]
You can either copy Array.prototype
's iterator or build something similar. We
will see how to do both:
Copying
class myClass {
constructor(obj) {
Object.keys(obj).forEach((key) => {
this[key] = obj[key];
});
}
}
myClass.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
[...new myClass({ 0: 1, 1: 2, 2: 3, length: 3 })]; // [1, 2, 3]
What the Array.prototype
iterator does is checks the length and yields each and
every index until the length is reached.
Let's try that by ourselves.
Rewriting
class myClass {
constructor(obj) {
Object.keys(obj).forEach((key) => {
this[key] = obj[key];
});
}
*[Symbol.iterator]() {
let currentIndex = 0;
while (currentIndex < this.length) {
yield this[currentIndex];
currentIndex++;
}
}
}
This defines a generator function that will keep track of the index that it was on and increment that every time. The index's value will then be yielded.