What are iterators?
In JavaScript, iterators provide a standard way to traverse elements of a collection, allowing you to access items one at a time. They are a fundamental concept behind iterable protocols, enabling constructs like the `for...of` loop and the spread operator.
What is an Iterator?
An iterator is an object that defines a sequence and potentially a return value upon its termination. More specifically, an iterator is any object that implements the Iterator Protocol. This protocol requires the object to have a next() method. When called, the next() method returns an object with two properties: value (the next item in the sequence) and done (a boolean indicating if the iteration is complete).
When done is false, the value property holds the next item in the sequence. When done becomes true, it signifies that the iteration has finished, and value (if present) is the optional return value of the iterator.
The Iterable Protocol
While an iterator dictates how to get the next item, an iterable is an object that defines how to produce an iterator. An object is considered iterable if it implements the Iterable Protocol, which means it must have a method accessible via the constant [Symbol.iterator]. This method, when called, must return an iterator.
Built-in JavaScript types like Array, String, Map, Set, and TypedArray are all built-in iterables. This is why you can use a for...of loop directly on them.
Why Use Iterators?
- Standardization: Provides a consistent interface for traversing different data structures.
for...ofLoop: Enables the use of thefor...ofloop on any iterable object.- Spread Syntax (
...): Allows iterables to be expanded into individual elements (e.g.,[...myIterable]). - Lazy Evaluation: Iterators can generate values on demand, which is useful for potentially infinite sequences or large datasets, as it avoids loading all data into memory at once.
- Custom Iteration Logic: Developers can define their own iteration behavior for custom objects.
Example: Custom Iterable/Iterator
Here's a simple example of a custom object that implements both the Iterable and Iterator protocols, allowing it to be used with a for...of loop.
function createRange(start, end) {
let current = start;
return {
[Symbol.iterator]() { return this; }, // Iterable protocol
next() { // Iterator protocol
if (current <= end) {
return { value: current++, done: false };
} else {
return { done: true };
}
}
};
}
const myRange = createRange(1, 3);
for (const num of myRange) {
console.log(num); // Outputs: 1, 2, 3
}
// Spread operator also works
const numbers = [...createRange(5, 7)];
console.log(numbers); // Outputs: [5, 6, 7]
In this example, createRange returns an object that is both an iterable (because [Symbol.iterator] returns this, meaning itself) and an iterator (because it has a next() method). Each call to next() increments current and returns the new value until end is exceeded.
Generators and Iterators
Generators (function*) provide a more convenient and readable way to create iterators in JavaScript. A generator function, when called, returns a special type of iterator called a Generator object. This object automatically implements the next() method and handles the state management (pausing and resuming execution with yield).