Iterables and for-of
Closely related to iterators, an iterable is an object with a Symbol.iterator
property. The well-known Symbol.iterator
symbol specifies a function that returns an iterator for the given object. All collection objects (arrays, sets, and maps) and strings are iterables in ECMAScript 6 and so they have a default iterator specified. Iterables are designed to be used with a new addition to ECMAScript: the for-of
loop.
I> All iterators created by generators are also iterables, as generators assign the Symbol.iterator
property by default.
At the beginning of this chapter, I mentioned the problem of tracking an index inside a for
loop. Iterators are the first part of the solution to that problem. The for-of
loop is the second part: it removes the need to track an index into a collection entirely, leaving you free to focus on working with the contents of the collection.
A for-of
loop works on an iterable and uses its Symbol.iterator
property to retrieve an iterator. Then, the for-of
loop calls next()
on that iterator each time the loop executes and stores the value
from the result object in a variable. The loop continues this process until the returned object’s done
property is true
. Here’s an example:
let values = [1, 2, 3];
for (let num of values) {
console.log(num);
}
This code outputs the following:
1
2
3
This for-of
loop first calls the Symbol.iterator
method on the values
array to retrieve an iterator. (The call to Symbol.iterator
happens behind the scenes in the JavaScript engine itself.) Then iterator.next()
is called, and the value
property on the iterator’s result object is read into num
. The num
variable is first 1, then 2, and finally 3. When done
on the result object is true
, the loop exits, so num
is never assigned the value of undefined
.
If you are simply iterating over values in an array or collection, then it’s a good idea to use a for-of
loop instead of a for
loop. The for-of
loop is generally less error-prone because there are fewer conditions to keep track of. Save the traditional for
loop for more complex control conditions.
W> The for-of
statement will throw an error when used on, a non-iterable object, null
, or undefined
.
Accessing the Default Iterator
You can use Symbol.iterator
to access the default iterator for an object, like this:
let values = [1, 2, 3];
let iterator = values[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 }"
This code gets the default iterator for values
and uses that to iterate over the items in the array. This is the same process that happens behind-the-scenes when using a for-of
loop.
Since Symbol.iterator
specifies the default iterator, you can use it to detect whether an object is iterable as follows:
function isIterable(object) {
return typeof object[Symbol.iterator] === "function";
}
console.log(isIterable([1, 2, 3])); // true
console.log(isIterable("Hello")); // true
console.log(isIterable(new Map())); // true
console.log(isIterable(new Set())); // true
console.log(isIterable(new WeakMap())); // false
console.log(isIterable(new WeakSet())); // false
The isIterable()
function simply checks to see if a default iterator exists on the object and is a function. The for-of
loop does a similar check before executing.
So far, the examples in this section have shown ways to use Symbol.iterator
with built-in iterable types, but you can also use the Symbol.iterator
property to create your own iterables.
Creating Iterables
Developer-defined objects are not iterable by default, but you can make them iterable by creating a Symbol.iterator
property containing a generator. For example:
let collection = {
items: [],
*[Symbol.iterator]() {
for (let item of this.items) {
yield item;
}
}
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}
This code outputs the following:
1
2
3
First, the example defines a default iterator for an object called collection
. The default iterator is created by the Symbol.iterator
method, which is a generator (note the star still comes before the name). The generator then uses a for-of
loop to iterate over the values in this.items
and uses yield
to return each one. Instead of manually iterating to define values for the default iterator of collection
to return, the collection
object relies on the default iterator of this.items
to do the work.
I> “Delegating Generators” later in this chapter describes a different approach to using the iterator of another object.
Now you’ve seen some uses for the default array iterator, but there are many more iterators built in to ECMAScript 6 to make working with collections of data easy.