Iterating ES6 Numbers

ES6 (or as the hipsters call it, “ES2015″) introduces the concept of iterators, which allow you to run through a collection of values one at a time. The interface for the iterator is the one you’d typically expect from other languages.

To learn more about iterators, see Chapter 4 of the Async & Performance title, as well as Chapter 2 of ES6 & Beyond, of my You Don’t Know JS book series.

Consider this code showing how to use an ES6 iterator:

var it = getMeAnIterator();

it.next();
it.next( 42 );
..

And the return value from the next(..) is always an object of the form:

{ done: true/false, value: .. }

Several of JS’s built-in data structures, most notably Array and String, are iterables, which means you can get an iterator from them to loop over its values.

To assist in iterating over such a data structure, ES6 also introduces the for..of loop. Using iterables together with for..of looks something like:

var a = [1, "hello", 42, "world!"];

for (var i of a) {
    console.log( i );
}
// 1
// "hello"
// 42
// "world!"
var s = "abc";
for (var c of s) {
    console.log( c );
}
// "a"
// "b"
// "c"

Cool, huh?

Note: You might think objects would come with a built-in iterator just like arrays do, but they don’t. The reasons are nuanced, so we won’t get into that rabbit hole here. But you could make your own, as you’ll learn about in the next section.

Custom Iterators

In addition to the built-in iterators that come on some data structures, you can also define your own. For example, let’s make an object that when iterated, returns only even numbers, from 0 up to 10:

var evens = {
    [Symbol.iterator]: function(){
        var i;
        
        return {
            next: function() {
                if (i == null) {
                    i = 0;
                    return { value: 0, done: false };    
                }
                else if (i <= 8) {
                    i += 2;
                    return { value: i, done: false };
                }
                else {
                    return { done: true };
                }
            }    
        };
    }
};

for (var i of evens) {
    console.log( i );
}
// 0
// 2
// 4
// 6
// 8
// 10

In this snippet, Symbol.iterator is referencing a special ES6 value of the type Symbol, which is a unique unguessable string value used to represent special meta properties. In this case, Symbol.iterator is the predefined value that represents a value's built-in iterator factory -- not the iterator itself but a function you call to get an iterator instance for the value.

For example, an array's built-in iterator factory can be accessed (or even overridden!) by:

var a = [1,2,3];

var it = a[Symbol.iterator]();

it.next();  // { value: 1, done: false }
..

So in our evens example above, we're declaring an iterator factory using [Symbol.iterator]: .. as the property name, instead of say a normal property like foo: ... The [ .. ] syntax in the property position in an object literal is another ES6'ism called "computed property names", which means you can put any normal JS expression inside the [ .. ] pair, and whatever it evaluates to is what will be used for the actual property name.

For example:

var a = "fo", b = "o";

var o = {
   [a + b]: 42
};

o.foo;  // 42

Back to the evens example, now we declare a function that returns the iterator when called, which is just an object with a next(..) function on it. That next(..) function just needs to return back the desired { value: .. , done: .. } tuple each time it's called.

Note: Technically, built-in iterators have a next(..) and a return(..) on them. In addition, generator iterators have a throw(..) as well. We don't need to worry about either of those for our purposes here, though.

That's it! You've made your own custom iterator. Now it's time to consume (use) it.

The for..of loop will automatically look for the value being iterated (evens in our evens example), and see if it has an iterable at the Symbol.iterator property location. If so, that function is called, and the iterator returned is used for the for..of loop processing.

It will keep looping until it receives a tuple from the iterator with done: true. Lastly, for each successful loop iteration, the value property of the tuple is assigned to the loop indexing variable (i in our evens example).

Iterating Numbers

OK, so now let's have a little fun with our new-found knowledge. What if we could make all numbers in JS able to be iterated? What would that mean?

The most natural thing I could think of is that you iterate from 0 up to (or down to, for negative numbers) and including the number. We could even let you jump in steps of more than 1 if you wanted to do it manually.

Here's what it would look like to use it:

for (var i of 7) {   // much nicer than:   for (var i=0; i<= 7; i++) {
    console.log( i );
}

Or manually iterate for more control:

var it = 8[Symbol.iterator]( 2 ); // default to stepping by 2

it.next();  // { value: 0, done: false }
it.next();  // { value: 2, done: false }

// now let's step by 4 (2 * 2)
it.next(2); // { value: 6, done: false }

it.next();  // { value: 8: done: false }
it.next();  // { done: true }

That's kinda neat! for (var i of 7) .. is a nice terse form of a typical for loop for the same task.

And the big reveal, how do we pull off such trickery? We add a custom iterator to the Number.prototype:

if (!Number.prototype[Symbol.iterator]) {
    Number.prototype[Symbol.iterator] = function(inc){
        var i, done = false, top = +this;
        
        // iterate positively or negatively?
        inc = Math.abs(Number(inc) || 1) * (top < 0 ? -1 : 1);
                
        return {
            // make the iterator itself an iterable!
            [Symbol.iterator]: function(){ return this; },

            next: function(step) {
                // increment by `step` (default: 1)
                step = Math.abs(Number(step) || 1);
                
                if (!done) {
                    // initial iteration always 0
                    if (i == null) {
                        i = 0;
                    }
                    // iterating positively
                    else if (top >= 0) {
                        i = Math.min(top,i + (inc * step));
                    }
                    // iterating negatively
                    else {
                        i = Math.max(top,i + (inc * step));
                    }
                    
                    // done after this iteration?
                    if (i == top) done = true;

                    return { value: i, done: false };
                }
                else {
                    return { done: true };
                }
            }
        };        
    };
}

Update: thanks to commenter ziyunfei, now we've made the iterator itself an iterable, which lets you use a manual instance of the iterator in any place where JS is actually expecting an iterable. For example, in the earlier examples using this Number iterator, you could do for (var i of 8[Symbol.iterator](2)) { .. }. Cool, huh!?

Should be pretty straightforward, similar to our previous discussion of custom iterators.

Note: Don't forget that ES6 features either require an ES6 browser or the use of a transpiler, such as 6to5. If your browser doesn't support some or all of the features shown in this blog post, try them out using their online REPL demo.

Number Ranges

Update: I added this section thanks again to commenter ziyunfei.

ES6 also adds a new ... (yes, three dots) operator that has several different uses/behaviors depending on the context it's used in. We will only cover one of them, which is that if used in front of a value that has an iterator, it automatically "spreads" all the returned values from that iterator out.

So, we can do this awesome trick:

var range1 = [ ...18 ];
range1; // [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]

var range2 = [ ...18[Symbol.iterator](3) ];
range2; // [0,3,6,9,12,15,18]

Now that is neat, huh!?

Summary

ES6 iterators and the for..of loop provide some nice syntactic support for iterating over collections of values, either real or computed. And the ... in the mix now lets us have syntactic support for number ranges!

Not all JS value types have their own iterators built-in, such as objects and numbers. But we can make our own. Almost makes you wonder why ES6 didn't include this number iterator, as cool as those tricks are!

Let the fun begin. What other custom iterations can you imagine?