It's been a long time... I shouldn't have left you... without a dope post to step to... It's been nearly 2 months since I wrote the last article about ES6 promises in late September. Since then I've been able to share what I've learned about ES6, speaking at Nodevember 2015 and Little Rock Tech Fest 2015.
At the end of the article about native ES6 promises, I mentioned that I would post about generators next. That was a bit premature. There are a couple of building blocks that we need to talk about: the new for-of
loop, iterators & iterables, and the new collections added in ES6. After that we should have everything we need to deep dive into the asynchronous world of generators. So let's talk about the new for-of
loop!
TL;DR
The new for-of
loop introduced with ES6 allows for iterating over an array (or any iterable) in a succinct fashion similar to how we can iterate over the keys of an object using for-in
.
let list = [8, 3, 11, 9, 6]
for (let value of list) {
console.log(value)
}
For the most part, there's nothing really too earth-shattering here. We just now get the functionality that exists in other programming languages and has been sorely lacking in JavaScript.
But keep on readin' to learn the ins and outs!
for
loop
ES3: Over the last two decades of JavaScript, developers have iterated over elements of an array using the simple for
loop.
var list = [8, 3, 11, 9, 6]
for (var i = 0; i < list.length; i++) {
console.log(list[i])
}
There's nothing wrong with this approach, but it just feels like a lot of code to write these days. We have to keep track of the loop counter variable (i
in the above example), tell it to increment by 1 with each iteration (i++
), and control when the iteration ends (i < list.length
).
But if we wanted to be totally accurate, we should write the code like this:
var list = [8, 3, 11, 9, 6],
length = list.length,
i
for (i = 0; i < length; i++) {
console.log(list[i])
}
This is because the JavaScript engine will actually hoist i
to the top of the function (see the article on block-level scoping for more on variable hoisting). Also in the previous for
loop, list.length
gets accessed with every iteration of the loop even though it doesn't change, so storing it in a length
variable is a tad bit more efficient. This all seems overkill when all we want to do is iterate over each element of the list
.
for-in
temptation
The If you've used other programming languages such as Python, you may be tempted to use for-in
with arrays for a more succinct syntax:
var list = [8, 3, 11, 9, 6],
i
// DON'T DO THIS!!!!
for (i in list) {
console.log(list[i])
}
But you SHOULD NOT DO THIS! Depending in the JavaScript engine, it could iterate in an arbitrary order. Furthermore, other properties on the array (such as length
) could also be included in the iteration (older version of Internet Explorer do this). Finally, the iteration variable (i
in the above example) is a String
, not a Number
so if you try to do any math with the variable (such as i+1
), you'll actually be preforming string concatenation instead of addition.
for-in
was exclusively intended for iterating over the enumerable keys of an object, and is not for iterating over arrays.
forEach
method
ES5: ECMAScript 5 introduced the forEach
instance method on arrays:
var list = [8, 3, 11, 9, 6]
list.forEach(function (value, i) {
console.log(value)
})
The syntax feels less verbose because we no longer need to maintain the loop control variable and the method automatically runs to completion of the array for us. But using forEach
has some unfortunate drawbacks.
With a normal for
loop you can break
to end the loop early. There isn't a way to end forEach
early. Including break
within the forEach
callback function will be a syntax error. It is only valid within loops.
Similarly, with a for
loop when we return
, we are exiting out of the entire function that the for
loop is in. However, putting a return
within the forEach
callback function just exits out of the callback function itself early. It's actually more or less equivalent to doing continue
in a for
loop, but far less intuitive. Including continue
in the forEach
call back function would be the same sort of syntax error we got with break
.
Lastly, only Array
objects have the forEach
method. Other array-like objects (such as arguments
or DOM NodeList
) unfortunately do not, leaving us to do "clever" tricks with Array.prototype.apply
.
for-of
loop
ES6: With the new for-of
loop, ES6 aims to bring the best parts from all three of the previous approaches.
let list = [8, 3, 11, 9, 6]
for (let value of list) {
console.log(value)
}
We get the succinct syntax of for-in
, the run-to-completion of forEach
, and the ability to break
, continue
, and return
of the simple for
loop. Now JavaScript has a loop control structure that is just as succinct as what you will find in Python, C# or Java.
Remember: for-of
is for iterating over arrays and for-in
is for iterating over the keys of objects.
Why a new operator?
So why the new for-of
operator? Why not just make for-in
work correctly with arrays? They look almost identical, right? Great questions!
One of the primary objectives for the TC39 committee when coming up with new ECMAScript features is maintaining backwards compatibility. There are trillions and trillions of lines of JavaScript code out there in the Wild Wild Web, and new versions of JavaScript cannot break that code. There are probably thousands (if not millions) of lines of JavaScript code that are intentionally or unintentionally relying on the brokeness of how for-in
works with arrays. Fixing for-in
to work how for-of
now works would have a side effect of breaking all of that code. So unfortunately we need a new operator.
for-of
and iterables
But for-of
doesn't just work for arrays. If it did, it probably wouldn't have been meaty enough to add to the ES6 specification. Other existing collections like the DOM NodeList
object, the arguments
object, and strings also work with for-of
. Just like with arrays, this makes it a little bit easier to iterate over these non-array objects.
When you use for-of
with a string, you iterate over each character in the string:
for (var char of 'Hello') {
console.log(char)
}
// output:
// H
// e
// l
// l
// o
ES6 also introduces some new collections (Map
, Set
, WeakMap
, and WeakSet
), which we'll discuss in detail in the next article. These work with for-of
as well. Essentially for-of
can iterate over any object that is iterable. That's where the power & worth of for-of
really come through. An object is iterable if it implements a default iterator. This probably won't make too much sense at the moment, but we'll learn all about iterators and iterables right after we cover the new collections.
JavaScript engine support
According to the ECMAScript 6 Compatibility table all major & modern JavaScript engines (transpilers, browsers and servers) support the for-of
operator.
Additional resources
Normally there would be a separate code examples page where you would find all the code used in an article, but there was hardly any substantial code used in this article. for-of
is pretty straightforward. That's probably also why there aren't any for-of
-specific ES6 Katas either.
The for-of
operator, iterators & iterables are heavily tied together, so there aren't any blogs or books that talk about for-of
separately like I've done here. for-of
can be used without custom iterators and most developers will just use it with arrays as described above, so I felt that it was useful as a topic on its own. It is pretty straightforward, but if you'd like to read some more:
- Iterables and iterators in Exploring ES6 by Axel Rauschmayer
- Iterators and Generators in Understanding ECMAScript 6 by Nicholas C. Zakas
- ES6 Iterators in Depth in ES6 in Depth by Nicolas Bevacqua
- ES6 In Depth: Iterators and the for-of loop in ES6 in Depth by Jason Orendorff
Coming up next...
I promise not to have another 2-month delay between articles. Up next will either be a deep dive into the 4 new collections added in ES6 (Map
, Set
, WeakMap
& WeakSet
) or a comprehensive look at iterators and iterables. There essentially is a circular dependency between the two topics (either one requires some understanding of the other), but I'm leaning towards talking about the collections first. You could get away without knowing anything about iterators and still use the new collections. Until then...