ES6 In Depth: Iterators and the for-of loop

ES6 In Depth is a series on new features being added to the JavaScript programming language in the 6th Edition of the ECMAScript standard, ES6 for short.

How do you loop over the elements of an array? When JavaScript was introduced, twenty years ago, you would do it like this:

<pre>
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
</pre>

Since ES5, you can use the built-in forEach method:

<pre>
myArray.forEach(function (value) {
console.log(value);
});
</pre>

This is a little shorter, but there is one minor drawback: you can’t break out of this loop using a break statement or return from the enclosing function using a return statement.

It sure would be nice if there were just a for-loop syntax that looped over array elements.

How about a forin loop?

<pre>
for (var index in myArray) { // don't actually do this
console.log(myArray[index]);
}
</pre>

This is a bad idea for several reasons:

  • The values assigned to index in this code are the strings "0", "1", "2" and so on, not actual numbers. Since you probably don’t want string arithmetic ("2" + 1 == "21"), this is inconvenient at best.
  • The loop body will execute not only for array elements, but also for any other expando properties someone may have added. For example, if your array has an enumerable property myArray.name, then this loop will execute one extra time, with index == "name". Even properties on the array’s prototype chain can be visited.
  • Most astonishing of all, in some circumstances, this code can loop over the array elements in an arbitrary order.

In short, forin was designed to work on plain old Objects with string keys. For Arrays, it’s not so great.

The mighty for-of loop

Remember last week I promised that ES6 would not break the JS code you’ve already written. Well, millions of Web sites depend on the behavior of forin—yes, even its behavior on arrays. So there was never any question of “fixing” forin to be more helpful when used with arrays. The only way for ES6 to improve matters was to add some kind of new loop syntax.

And here it is:

<pre>
for (var value of myArray) {
console.log(value);
}
</pre>

Hmm. After all that build-up, it doesn’t seem all that impressive, does it? Well, we’ll see whether forof has any neat tricks up its sleeve. For now, just note that:

  • this is the most concise, direct syntax yet for looping through array elements
  • it avoids all the pitfalls of forin
  • unlike forEach(), it works with break, continue, and return

The forin loop is for looping over object properties.

The forof loop is for looping over data—like the values in an array.

But that’s not all.

Other collections support for-of too

forof is not just for arrays. It also works on most array-like objects, like DOM NodeLists.

It also works on strings, treating the string as a sequence of Unicode characters:

<pre>
for (var chr of "😺😲") {
alert(chr);
}
</pre>

It also works on Map and Set objects.

Oh, I’m sorry. You’ve never heard of Map and Set objects? Well, they are new in ES6. We’ll do a whole post about them at some point. If you’ve worked with maps and sets in other languages, there won’t be any big surprises.

For example, a Set object is good for eliminating duplicates:

<pre>
// make a set from an array of words
var uniqueWords = new Set(words);
</pre>

Once you’ve got a Set, maybe you’d like to loop over its contents. Easy:

<pre>
for (var word of uniqueWords) {
console.log(word);
}
</pre>

A Map is slightly different: the data inside it is made of key-value pairs, so you’ll want to use destructuring to unpack the key and value into two separate variables:

<pre>
for (var [key, value] of phoneBookMap) {
console.log(key + "'s phone number is: " + value);
}
</pre>

Destructuring is yet another new ES6 feature and a great topic for a future blog post. I should write these down.

By now, you get the picture: JS already has quite a few different collection classes, and even more are on the way. forof is designed to be the workhorse loop statement you use with all of them.

forof does not work with plain old Objects, but if you want to iterate over an object’s properties you can either use forin (that’s what it’s for) or the builtin Object.keys():

<pre>
// dump an object's own enumerable properties to the console
for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}
</pre>

Under the hood

“Good artists copy, great artists steal.” —Pablo Picasso

A running theme in ES6 is that the new features being added to the language didn’t come out of nowhere. Most have been tried and proven useful in other languages.

The forof loop, for example, resembles similar loop statements in C++, Java, C#, and Python. Like them, it works with several different data structures provided by the language and its standard library. But it’s also an extension point in the language.

Like the for/foreach statements in those other languages, forof works entirely in terms of method calls. What Arrays, Maps, Sets, and the other objects we talked about all have in common is that they have an iterator method.

And there’s another kind of object that can have an iterator method too: any object you want.

Just as you can add a myObject.toString() method to any object and suddenly JS knows how to convert that object to a string, you can add the myObject[Symbol.iterator]() method to any object and suddenly JS will know how to loop over that object.

For example, suppose you’re using jQuery, and although you’re very fond of .each(), you would like jQuery objects to work with forof as well. Here’s how to do that:

<pre>
// Since jQuery objects are array-like,
// give them the same iterator method Arrays have
jQuery.prototype[Symbol.iterator] =
Array.prototype[Symbol.iterator];
</pre>

OK, I know what you’re thinking. That [Symbol.iterator] syntax seems weird. What is going on there? It has to do with the method’s name. The standard committee could have just called this method .iterator(), but then, your existing code might already have some objects with .iterator() methods, and that could get pretty confusing. So the standard uses a symbol, rather than a string, as the name of this method.

Symbols are new in ES6, and we’ll tell you all about them in—you guessed it—a future blog post. For now, all you need to know is that the standard can define a brand-new symbol, like Symbol.iterator, and it’s guaranteed not to conflict with any existing code. The trade-off is that the syntax is a little weird. But it’s a small price to pay for this versatile new feature and excellent backward compatibility.

An object that has a [Symbol.iterator]() method is called iterable. In coming weeks, we’ll see that the concept of iterable objects is used throughout the language, not only in forof but in the Map and Set constructors, destructuring assignment, and the new spread operator.

Iterator objects

Now, there is a chance you will never have to implement an iterator object of your own from scratch. We’ll see why next week. But for completeness, let’s look at what an iterator object looks like. (If you skip this whole section, you’ll mainly be missing crunchy technical details.)

A forof loop starts by calling the [Symbol.iterator]() method on the collection. This returns a new iterator object. An iterator object can be any object with a .next() method; the forof loop will call this method repeatedly, once each time through the loop. For example, here’s the simplest iterator object I can think of:

<pre>
var zeroesForeverIterator = {
[Symbol.iterator]: function () {
return this;
},
next: function () {
return {done: false, value: 0};
}
};
</pre>

Every time this .next() method is called, it returns the same result, telling the forof loop (a) we’re not done iterating yet; and (b) the next value is 0. This means that for (value of zeroesForeverIterator) {} will be an infinite loop. Of course, a typical iterator will not be quite this trivial.

This iterator design, with its .done and .value properties, is superficially different from how iterators work in other languages. In Java, iterators have separate .hasNext() and .next() methods. In Python, they have a single .next() method that throws StopIteration when there are no more values. But all three designs are fundamentally returning the same information.

An iterator object can also implement optional .return() and .throw(exc) methods. The forof loop calls .return() if the loop exits prematurely, due to an exception or a break or return statement. The iterator can implement .return() if it needs to do some cleanup or free up resources it was using. Most iterator objects won’t need to implement it. .throw(exc) is even more of a special case: forof never calls it at all. But we’ll hear more about it next week.

Now that we have all the details, we can take a simple forof loop and rewrite it in terms of the underlying method calls.

First the forof loop:

<pre>
for (VAR of ITERABLE) {
STATEMENTS
}
</pre>

Here is a rough equivalent, using the underlying methods and a few temporary variables:

<pre>
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
VAR = $result.value;
STATEMENTS
$result = $iterator.next();
}
</pre>

This code doesn’t show how .return() is handled. We could add that, but I think it would obscure what’s going on rather than illuminate it. forof is easy to use, but there is a lot going on behind the scenes.

When can I start using this?

The forof loop is supported in all current Firefox releases. It’s supported in Chrome if you go to chrome://flags and enable “Experimental JavaScript”. It also works in Microsoft’s Spartan browser, but not in shipping versions of IE. If you’d like to use this new syntax on the web, but you need to support IE and Safari, you can use a compiler like Babel or Google’s Traceur to translate your ES6 code to Web-friendly ES5.

On the server, you don’t need a compiler—you can start using forof in io.js (and Node, with the --harmony option) today.

(UPDATE: This previously neglected to mention that forof is disabled by default in Chrome. Thanks to Oleg for pointing out the mistake in the comments.)

{done: true}

Whew!

Well, we’re done for today, but we’re still not done with the forof loop.

There is one more new kind of object in ES6 that works beautifully with forof. I didn’t mention it because it’s the topic of next week’s post. I think this new feature is the most magical thing in ES6. If you haven’t already encountered it in languages like Python and C#, you’ll probably find it mind-boggling at first. But it’s the easiest way to write an iterator, it’s useful in refactoring, and it might just change the way we write asynchronous code, both in the browser and on the server. So join us next week as we look at ES6 generators in depth.

About Jason Orendorff

More articles by Jason Orendorff…


26 comments

  1. Brett Zamir

    Great article…

    Might just mention that Array.some can at least deal with the missing “break” of Array.forEach by returning true (and “continue” can be done by returning false). Unfortunately, however, there are no array extras which both allow one to prematurely break execution as well as reduce to a value, allowing for the result to be “return”-ed.

    Personally, I like the easier reusability (and functional feel) of array extras over for-of (dropping in a function name is easier than building an iteratable object), and wish there were Object.forEach (and String.forEach, etc.) which both allowed for iteration of vanilla object values and allowed for early breaking of execution.

    One question: Does for-of iterating a string indeed loop once for each Unicode character or is it for each Unicode code point?

    April 30th, 2015 at 17:31

    1. Jason Orendorff

      > Personally, I like the easier reusability (and functional feel) of array extras over for-of (dropping in a function name is easier than building an iteratable object)

      Functional techniques are awesome. The array extras are often an improvement over procedural code, and for-of won’t change that.

      But next time you’re using .reduce() and it starts to get hairy, try rewriting it as a for-of loop. Sometimes the clearest code is straightforward procedural code that just bluntly tells the computer what to do. Sometimes the best way to represent state is a variable. :)

      In particular, once for-of is widely supported, I’m not sure there will be any reason to use .forEach(). The loop syntax is just clearer and shorter, and works better with the rest of the language. (You’re not fooling anybody, .forEach(). We know you’re a procedural construct!)

      Anyway—if you like functions, I’ll have good news for you too, in a few weeks. There will be example code with fixed point combinators. It’s going to be crazy.

      > One question: Does for-of iterating a string indeed loop once for each Unicode character or is it for each Unicode code point?

      It really is each Unicode character.

      May 1st, 2015 at 08:31

  2. Luke

    Nice, but will it work in every browser? Underscore is great for better syntax for loops and maps, etc., works even in old IE for example.

    April 30th, 2015 at 20:12

  3. Šime Vidas

    [SPOILER] Next week’s topic are generators.

    April 30th, 2015 at 20:53

  4. ziyunfei

    Looking forward to the next article, and BTW, “twenty years ago”, JS1.0 didn’t have arrays. :-D

    May 1st, 2015 at 00:46

  5. Andrea Giammarchi

    And don’t forget the polyfill ;-)

    https://github.com/WebReflection/get-own-property-symbols

    May 1st, 2015 at 07:17

  6. Andrea Giammarchi

    @Brett

    > Unfortunately, however, there are no array extras which both allow one to prematurely break

    technically false, you can always exit from Array extras methods.

    [1,2,3].forEach(function (v,i,a) {
    console.log(i, v);
    a.length = 0;
    });

    it’s dirty and it mutates, but it works for runtime checks at least ;-)

    > execution as well as reduce to a value, allowing for the result to be “return”-ed.

    isn’t that what Array.prototype.reduce is about?

    var o = [‘a’, ‘b’, ‘a’].reduce(function (o, k) {
    o[k] = (o[k] | 0) + 1;
    return o;
    }, {});

    console.log(o); // {a: 2, b: 1}

    Cheers

    May 1st, 2015 at 07:22

  7. Daniel Herman

    Great article! One nit – since we’re talking ES6, I feel like your examples should be using `let` rather than `var` to illustrate block scoping in the for of loops. Other than that… <3

    May 1st, 2015 at 08:47

  8. Igor

    What is the definition of array-like object?

    May 1st, 2015 at 11:15

    1. Jason Orendorff

      @Igor: Oh, good question. It means an object with a `.length` property and elements that can be accessed using `object[index]` syntax.

      May 1st, 2015 at 14:16

  9. Ken Arnold

    What is an idiomatic way to get both the index (or key) and value, like ‘enumerate(iterable)’ in Python? The “twenty years ago” initial example gives you the index for free, forEach gives you the index as the second parameter to the callback, and in CoffeeScript you can say ‘for item, index in array’.

    (Bonus: Python’s ‘enumerate’ takes an optional second parameter, the starting index; I’ve found this useful when iterating over slices of arrays.)

    May 1st, 2015 at 18:18

    1. Vlad

      @Ken: you can write your own iterator method like this:
      Array.prototype.myIterator = function* (startIdx = 0) {
      while(startIdx < this.length) {
      if(this.hasOwnProperty(startIdx)) {
      yield [this[startIdx], startIdx];
      }
      startIdx++;
      }
      };

      And then use it in for-of loops:
      for(var [val, idx] of [0,2,4,6,8].myIterator(1)) {
      console.log(val, idx);
      }

      May 1st, 2015 at 22:32

      1. Jason Orendorff

        Sorry the site ate your indentation. I don’t know how to fix it.

        I put a copy into a codepen, so people can experiment with it: http://codepen.io/anon/pen/GJJemX

        May 3rd, 2015 at 08:54

    2. Jason Orendorff

      Ken: In ES6, Arrays have an .entries() method that’s like enumerate(). It returns an iterator that produces [index, value] pairs. Like for-of, it’s implemented in Firefox, Chrome, io.js, Node.

      But this only solves the problem for arrays. The standard library doesn’t have Python’s zip() either. Someone is going to have fun writing a really nice itertools library for JS…

      May 3rd, 2015 at 09:03

      1. Siva

        Thanks for the great article… Why we didn’t include support for index in ‘for of’ by default? Just wondering what might be the reason.

        May 4th, 2015 at 01:29

        1. Jason Orendorff

          Values are the most common use case by far. Note that enumerate() isn’t the default in Python either (or any other language I know of that has “foreach” loop syntax).

          May 4th, 2015 at 12:01

      2. Nick Fitzgerald

        https://github.com/fitzgen/wu.js

        Already wrote itertools for JS ;)

        May 14th, 2015 at 05:43

  10. Martin Rinehart

    Oh, dear. Not the conventional for/in, again. Please! This has been wrong, and repeated, for years.

    As always, for/in is the best way to loop through Arrays in almost all circumstances prior to ES6. Let me go through your three points in reverse order.

    You mention (third) that for-in may return elements out of order. This is only true with certain antique browsers (which certainly do not support ES6 extensions). If one used an array literal, or created an empty array and push() ed values it was never true.

    Second, I edited the Mozilla glossary to include non-numeric named array subcripts as “expando” properties, as I knew exactly what you meant and your example (“name” as an Array index) was perfectly clear. If there are such “expando” elements in arrays, there is a problem. Whether a loop should include or ignore them is a question. For-in will include them. Using a unary plus will ferret them out. The real problem is that someone has been doing some really bad coding adding such properties and this should be traced and eradicated at the source. Using a loop that passes over these offensive elements allows old bugs to lurk. Never a good idea.

    First, you’re right that for/in uses property names. This is the most efficient way to loop since the Array is built on property names. (Unarguably, a major deficiency in JS, but ES6 doesn’t address it. For-of doesn’t address it. Think WebGL for a case where it REALLY matters.) To say that you could then do something really dumb, like arithmetic, with these name indexes is correct. But is this a practical problem? Or an example of “new to JavaScript, needs to learn the basics”?

    The for-in loop is the only way to loop that correctly handles sparse arrays, which JS Array objects are. In a language intended to handle UI, allowing for the deletion of array elements is mandatory. How many old C-style loops have you seen correctly protected with ( arr[index] !== undefined ) code? For-in handles the deleted elements nicely.

    I haven’t got to exploring the for-of loop to see if it will handle the wonderful, ambiguous “undefined” correctly. (“undefined” could be the value of a defined property, or it could be that there is no property defined by a given name.) You might add this consideration to your article.

    I’ve written about this in more detail,

    http://martinrinehart.com/frontend-engineering/engineers/javascript/arrays/sparse-arrays.html

    The bottom line is that you should almost never use the C-style arithmetic loop on an array. For/In is almost always better and I’m not sure whether for/of will be an improvement.

    May 3rd, 2015 at 11:13

    1. Jason Orendorff

      Thanks for your comments! I can’t answer all your points that deserve answering, I’m afraid, but just a few:

      3. In shipping Firefox, for-in sometimes enumerates array element ids in insertion order rather than numeric order. It might be that only very sparse arrays are affected, but I’m not sure.

      2. I agree that expandos shouldn’t be present on arrays, but they sometimes are, as in the array returned by RegExp.prototype.exec().

      1. I think language gotchas are a practical problem. No amount of developer education prevents occasional mistakes, and even the “dumb” mistakes cost real time.

      One more note on speed. Arrays elements these days are *not* internally represented as a table of properties. The internal representation is now quite array-like. So the C-style loop can be something like 400x faster than for-in on arrays! (for-of is not as fast as it can be at the moment, but it’s already 20x faster than for-in, and we’re still optimizing it.)

      May 4th, 2015 at 12:53

  11. Phil Stricker

    Great to see for-of coming to ES6. I don’t know how many times I’ve had to refactor code using Array.forEach when the need to break out of the loop presents itself.

    @Luke, it might not be supported in “every browser” but adding a small polyfill for something is much more appealing/performant than loading an entire library for coverage.

    May 5th, 2015 at 18:24

  12. Fedge

    Still going to have to use Mootools for most stuff because browser support for all the new stuff is going to take years to catch up (people still using old versions of IE, mostly). Hopefully the frameworks people use will be updated to take advantage of all this stuff internally where it speeds things up.

    For situations where people can use the language directly without worrying about browser discrepancies (like in Firefox add-ons) the new JS features will be awesome.

    May 6th, 2015 at 14:15

  13. Oleg

    Amazing. FF seems to support the new ES6 syntax. Chrome doesn’t. Try to copy and execute the following code in http://www.squarefree.com/jsenv/ It works in Firefox 36.

    var myIterableObject = {
    value: 5,

    [Symbol.iterator]: function () {
    return this;
    },

    next: function () {
    if (this.value < 0) {
    return {done: true, value: 0};
    }

    var ret = {done: false, value: this.value};
    this.value–;
    return ret;
    }
    };

    for (var v of myIterableObject) {
    print(v);
    }

    May 7th, 2015 at 00:00

    1. Jason Orendorff

      Sorry, the comment system eats code. :-\ I copied it over here: http://codepen.io/anon/pen/oXjRPb

      I thought this was enabled by default in Chrome, but it’s not. You have to go to chrome://flags and click “Enable Experimental JavaScript”. Thanks for catching this. I’ll update the post.

      May 7th, 2015 at 08:46

  14. subzey

    Looks like there’s a small typo ” how for-in works” code: thevariable is called both `result` and `$result`.

    May 13th, 2015 at 13:49

    1. Jason Orendorff

      Oops, you’re right. I fixed it. Thanks for pointing it out!

      May 13th, 2015 at 13:57

      1. subzey

        Thank you for your post!

        May 13th, 2015 at 14:03

Comments are closed for this article.