Iterables & Iterators in JavaScript
Fri Dec 01 2023
Iterables are objects that can be iterated by
for..of
loop and can also return iterators explicitly. The best examples areArray
,Map
andSet
data structures in JavaScript.
Symbol.iterator
Symbol.iterator
is a well known system symbol in JavaScript. when
any object has a property of Symbol.iterator
then that object
can be iterated over for..of
loop. let's make our iterator to
understand how it works.
const range = {
from: 1,
to: 10,
};
// We want the for..of to work:
// for(let num of range) ...
Currently, the range object does not have Symbol.iterator
property, Therefore if we try using for..of
loop it will throw an error. As we discussed to make the range object iterable we need to add the iterable property.
Note :
Remember the Symbol.iterator
must return an object having the next method.
const range = {
from: 1,
to: 5,
};
range[Symbol.iterator] = function () {
let start = this.from;
let end = this.to;
return {
next() {
if (start <= end) return { done: false, value: start++ };
//^closure applied
return { done: true, value: undefined };
},
};
};
for (let num of range) {
console.log(num); // 1 2 3 4 5
}
Similarly, you can add the property to an object and make it iterate as you like. If you have noticed string
type is iterable with for..of
loop, it also has implemented the property and returns characters at each iteration.
Note :
Remember that the next method should return the object having done
and value
property.
const str = "Hello";
for (let char of str) {
console.log(char); // H e l l o
}
Explicit iterator
You can also directly access the Symbol.iterator
property and call it and it will return an iterator object in which you have next()
method.
const itr = range[Symbol.iterator]();
while (true) {
const { done, val } = itr.next();
if (done) break;
console.log(val); // 1 2 3 4 5
}
This is also helpful when you want only a few items from the iterator or the iterator is infinitely long.
Spread Operator (...)
You might be using the spread operator very often but if you keenly observe the spread operator works based on the Symbol.iterator
property only. it also iterates under the hood and provides you with spread values. let's recall the spread operator and let's see whether our range
object can do it or not.
const arr = [1, 2, 3];
console.log(...arr); // 1, 2, 3
const str = "hello";
console.log(...str); // h, e, l, l, o
// our range obj
console.log(...range); // 1, 2, 3, 4, 5
Yes, it will work because our range object has an iterator property. you can assign it to an array or other data structure or pass it as a spread parameter etc.
Generator function function\*
Regular functions return only one, single value (or nothing). Generators can return (“yield”)
multiple values, one after another, on-demand. They work great with iterables, allowing you to create data streams with ease.
Generators make your life easy to make iterators. To create a generator, we need a special syntax construct: function\*
, the so-called “generator function”.
function* generateRange() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
You can also specify Symbol.iterator
with a generator function and it not only reduces code but also enhances readability.
Let's rewrite our range iterator with the generator function.
const range = {
from: 1,
to: 10,
};
range[Symbol.iterator] = function* () {
let start = this.from;
let end = this.to;
while (start <= end) {
yield start;
start++;
}
};
// still works the same for..of loop
for (let num of range) {
console.log(num); // 1 2 3 4 5
}
// explicit iterator
const generator = range[Symbol.iterator]();
generator.next(); // { "value": 1, "done": false }
Summary
Objects that can be used in for..of are called iterable.
- Technically, iterables must implement the method named Symbol.iterator.
- The result of
obj[Symbol.iterator]\()
is called an iterator. It handles further iteration processes. - An iterator must have the method named
next()
that returns an object with done and next, here done:true denotes the end of the iteration process, otherwise the value is the next value.
- The result of
- The Symbol.iterator method is called automatically by for..of, but we also can do it directly.
- Built-in iterables like strings or arrays, also implement Symbol.iterator.
- String iterator knows about surrogate pairs.