JavaScript Iterators and Iterables
JavaScript provides a powerful way to traverse collections of data with the help of iterators and iterables. These concepts are fundamental when working with loops, arrays, or even custom objects that you want to iterate over.
An iterator is an object that enables you to iterate over a collection of data, such as an array, string, or even a custom object. It provides a standardized way to access the elements of an iterable one by one.
An iterator object must implement the following methods:
next()
: Returns the next item in the collection in the form of an object { value, done }
:
value
: The next item in the collection.done
: A boolean that indicates whether the collection has been fully traversed (true
if all items have been consumed).
const numbers = [1, 2, 3];
const iterator = numbers[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
Here, we manually create an iterator for the array numbers
by calling Symbol.iterator
and use next()
to get each value one by one. The done
property indicates when the iteration has finished.
An iterable is any object that can be iterated over, meaning you can use the for...of
loop or other iterator-based methods to traverse its elements. For an object to be iterable, it must implement the Symbol.iterator
method, which returns an iterator.
Built-in iterables in JavaScript include:
for...of
const fruits = ["apple", "banana", "cherry"];
for (let fruit of fruits) {
console.log(fruit);
}
// Output:
// apple
// banana
// cherry
The for...of
loop automatically uses the iterable's Symbol.iterator
method under the hood to get the next item until the iteration is complete.
While the terms iterator and iterable are related, they are different concepts.
Feature | Iterable | Iterator |
---|---|---|
Definition | An object that can be iterated over (it has a Symbol.iterator method). |
An object that provides the next() method to traverse through data. |
Example | Arrays, Strings, Maps, Sets | The object returned by Symbol.iterator |
Access Method | Accessed via for...of , spread syntax , or Array.from() |
Accessed via next() method. |
Property | Must have a Symbol.iterator method. |
Must have a next() method. |
In short:
Symbol.iterator
method, allowing it to be used in for...of
loops and other iterable-based operations.next()
method, enabling you to retrieve the next item in the iterable.You can create your own iterables by defining the Symbol.iterator
method on an object. This allows you to control how an object is iterated.
const myIterable = {
data: ["JavaScript", "Python", "Ruby"],
[Symbol.iterator]: function() {
let index = 0;
const data = this.data;
return {
next: function() {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
for (let language of myIterable) {
console.log(language);
}
// Output:
// JavaScript
// Python
// Ruby
In this example:
myIterable
with a Symbol.iterator
method.next()
method is used to return the next item from the data
array.JavaScript provides several built-in iterables such as Array
, String
, Map
, and Set
. You can directly use these in for...of
loops or convert them to arrays using methods like Array.from()
.
const message = "Hello, World!";
for (let char of message) {
console.log(char);
}
// Output:
// H
// e
// l
// l
// o
// ,
// (space)
// W
// o
// r
// l
// d
// !
In this case, the string "Hello, World!"
is an iterable, and we can use for...of
to loop through each character.
Array.from()
with Iterables
const numbers = new Set([1, 2, 3, 4]);
const arr = Array.from(numbers);
console.log(arr); // [1, 2, 3, 4]
In this case, we use Array.from()
to convert a Set
(which is an iterable) into an array.
Generators are a special type of iterable in JavaScript. They allow you to define custom iterators using the function*
syntax. Generators are often used when you want to create a sequence of values on demand (lazily).
function* generateNumbers() {
yield 1;
yield 2;
yield 3;
}
const gen = generateNumbers();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
Here:
function*
syntax defines a generator function.yield
keyword is used to return values one at a time.next()
is used to get each yielded value from the generator.Generators are ideal when you want to generate values lazily, meaning values are produced only when needed, which can help with performance when dealing with large data sets.
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const gen = fibonacci();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
This generator produces Fibonacci numbers on demand.
You can create custom data structures (like linked lists, trees, or queues) that implement the iterable protocol, allowing them to be used in for...of
loops and other iterable operations.
JavaScript also supports asynchronous iterators using for-await-of
, allowing you to iterate over async data, like data from an API, asynchronously.
async function* asyncGenerator() {
yield "first";
yield "second";
yield "third";
}
(async () => {
for await (let value of asyncGenerator()) {
console.log(value);
}
})();
In this example, for-await-of
is used to handle asynchronous data with a generator function.