Implementing Custom Iteration with JavaScript Protocols
Understanding Iteration Protocols
In JavaScript, the iteration protocol consists of two parts: the iterator protocol and the iterable protocol. An object conforms to the iterator protocol if it has a next() method that returns an object with value and done properties. To be iterable, an object must implement a method under the well-known symbol Symbol.iterator, which returns a valid iterator.
Manual Iterator Example
Consider a custom range iterator:
const range = {
start: 1,
end: 5,
next() {
if (this.start < this.end) {
return { value: this.start++, done: false };
}
return { done: true };
}
};
This iterator works when manually calling next(), but cannot be used in for...of loops because it lacks the Symbol.iterator method.
Making Objects Iterable
To enable for...of usage, atttach a Symbol.iterator method that returns the iterator itself:
range[Symbol.iterator] = function () {
return this;
};
Now for (const val of range) { ... } works as expected.
Iterating Plain Objects
Plain objects aren't iterable by default. We can make them iterable by defining Symbol.iterator:
const data = { x: 10, y: 20, z: 30 };
data[Symbol.iterator] = function* () {
for (const key of Object.keys(this)) {
yield [key, this[key]];
}
};
This allows destructuring key-value pairs directly in loops:
for (const [k, v] of data) {
console.log(k, v);
}
Using Generators for Simplicity
Generator functions automatically satisfy both protocols. The following generator yields indexed array elements without mutating the original array:
function* enumerate(arr) {
for (let i = 0; i < arr.length; i++) {
yield [arr[i], i];
}
}
const letters = ['a', 'b', 'c'];
for (const [value, index] of enumerate(letters)) {
console.log(value, index);
}
This approach avoids side effects like modifying built-in prototypes, which could break other code relying on standard iteration behavior (e.g., array destructuring).
Built-in Iterable Types
Native types like Array, String, Map, and Set are iterable because their prototypes include a Symbol.iterator method. This enables seamless use with for...of, spread syntax (...), and destructuring.