Looping. You know the drill. You have a collection (i.e. an array or object) of something and you want to loop over the collection, gaining access to each individual thing or the index/key indicating where it is located in the collection. This is looping or "iteration." It's one of the core tasks of any language and, in JavaScript 2015 (aka ES6), it is getting an upgrade.
In this article I am going to present and examine the evolution of JavaScript looping by reviewing ES3 and ES5 looping. I will then be discussing the updates to looping found in JavaScript 2015. After reading this article I think you'll agree that the updates are significant and worth learning about.
In ES3 several looping statements were available:
while
loopfor
loopOf the available looping statements listed above, the two most commonly used statements were likely the...
for
loop (for looping over an array)
var arr = [ 'a', 'b', 'c' ];
for (var i=0; i<arr.length; i++){
console.log(i+' : '+arr[i]);
}
...and the...
for-in
loop (for looping over an object, don't use on arrays)
var person = {fname:'John', lname:'Doe', age:25};
for (var prop in person){
if (Object.prototype.hasOwnProperty.call(person, prop)){
console.log(prop+' : '+person[prop]);
}
}
Now, I am just going to say it. I hate the "for loop" and I don't much care for the cruft of eliminating inherited properties when using the "for-in" loop. I don't think I am alone either. How do I know this? Well, just consider that almost every JavaScript utility under the sun, years ago, added some sort of abstraction for looping over arrays and objects. Most of these abstractions even offered a single interface for looping over either (i.e. a generic collection).
The fact that most, if not all, third-party JavaScript libraries and frameworks internally used a looping abstraction and offer a looping abstraction as part of their API was an obvious sign that ES3 was lacking. The authors of the next version (i.e. ES5) knew they had to address the issue to some degree.
As previously stated, third-party JavaScript libraries and frameworks started offering non-standard looping syntax and features. These abstractions have became a staple, if not the base, of most JavaScript libraries today or in the past. This probably contributed to the decision for ES5 to build in some additional looping power.
The following new array methods were added to ES5:
[1,2,3].every()
[1,2,3].filter()
[1,2,3].forEach()
[1,2,3].map()
[1,2,3].reduce()
[1,2,3].some()
Using these new methods, developers had new options available for looping over arrays and preforming common array tasks. For example, one could now loop over an array using .forEach()
instead of the for
loop.
var arr = [ 'a', 'b', 'c' ];
arr.forEach(function(elem,i){
console.log(i+' : '+elem);
});
In terms of looping over an Object
object, the fact that ES5 offered Object.keys
, which returns an array, deserves an honorable mention here too. Using Object.keys
and the forEach()
array method one could easily loop over an Object
object's own properties without using a for in
loop (Note: inherited properties are not being accessed)
var person = {fname:'John', lname:'Doe', age:25};
Object.keys(person).forEach(function(prop){
console.log(prop+' : '+person[prop]);
});
//Note a "for in" loop is still a viable alternative, you just have //to deal with inherited properties like we did in ES3
The small array looping evolution found in ES5, and the addition of Object.keys
, while a step in the right direction was still lacking when compared to offerings found in other languages. JavaScript developers needed more and the next version of JavaScript has delivered.
Before I begin, I would like to remind you that if you are not already aware, you can start using the newest version of JavaScript in a browser today. Transpilers, like Babel or TypeScript with help from core.js, make this a reality. I discussed this briefly in my previous article, "Six Steps for Approaching the Next JavaScript". You might pause and devour some of that if you haven't been paying attention to ES6.
All of the code in the remainder of this article, where possible without error, is using Babel and core.js via JS Bin to make the newest version of JavaScript runnable in modern browsers. Babel and core.js was chosen because it offers the highest degree of compatibility with the specification. With that said, let's dive into a monumental evolution in JavaScript looping.
New to JavaScript are two conventions (i.e. protocols) called iterable and iterator. These conventions are baked into the language. Now, given these are conventions, you can also bake these protocols into any user defined object as well. More on that later.
For now, just keep in mind that these two new conventions play a major role in looping over values in ES6, as well as, new syntactical features (i.e spread operator, destructuring, yeild*
etc.) that accept iterable values.
I am not going to dive deeply into the details of each of these new protocols in this article. Others have already done a fine job explaining the details in-depth and I don't want to sidetrack anyone new to the topic with too much detail. Instead, I am simply going to show you code, which I believe most JavaScript developers will understand, that demonstrates what is possible in ES6 in terms of looping. But, the truth of it is, much of what I am passing along here is only the tip of the iceberg.
The following JavaScript values in ES6 are iterable by default because their prototype objects all provide access to an iterator method.
Array
String
Map
(new in ES6)Set
(new in ES6)NodeLists
and HTMLCollections
from the DOMarguments
What exactly does that mean? To be iterable? Well for one, it means that each of the values listed above can be looped by way of the new for-of statement. The "for-of" statement was specifically added to ES6 to loop over iterable values.
I think it is important to note, after the last code example, that only the Array
, Map
, and Set
values have the entries()
, keys()
, and values()
methods to help navigate data.
No idea what a generator is? Check out the description on MDN.
var generator1 = function*(){
yield 1;
yield 2;
yield 3;
}();
for(let y of generator1){
//iterable, use "for-of" to yield values
console.log(y); //logs 1,2,3
};
var generator2 = function*(){//iterator, use next() to yield
yield 1;
yield 2;
yield 3;
}();
console.log(generator2.next()); //{"value":1,"done":false}
console.log(generator2.next()); //{"value":2,"done":false}
console.log(generator2.next()); //{"value":3,"done":false}
console.log(generator2.next()); //{"done":true}
//Note: once a generator has been "looped" it can't be looped again, so to speak
A few of things to note about all these iteration examples, which I believe make them ideal:
break
, continue
, and return
).Iterable values can also be looped over using an iterator. This makes it possible to step through, one by one, each value in an iterable (we've actually already seen this done in the generator example above).
As an example, any of the previous code examples demonstrating the for-of statement can also be stepped through by invoking the objects inherited [Symbol.iterator]()
function returning an iterator interface which contains a next()
method to step through the items to be looped (this excludes a generator, just use next()
).
Below, I demonstrate how using the iterator next()
method you can step one by one through items in an array.
Like I mentioned earlier, the possibility of what is iterable is actually limitless given that the protocols can be implemented on any JavaScript object. But keep in mind, by design Object
objects are not designed to be iterable.
To iterate over an Object
object, one can still use the looping mechanisms found in ES3 or ES5 (i.e. for-in loop or Object.keys
and forEach()
). Or, as of ES6, you do have the option of turning a plain Object
object into an iterable object.
In the code example below I employ the iterable conventions/protocol on a custom sentence Object
object.
User-defined iterables have some simple parts that shouldn't be difficult to mechanically understand if you are a JavaScript veteran. But, realistically, no matter how good you are at JavaScript, if these concepts are new, they can be surprisingly challenging to grok.
If you are new to these concepts, I would read and re-read the following resources on the new iteration protocols.
The following code examples demonstrate several language situations where the iteration protocols are put to use or expected (I'll refrain from providing another code example demonstrating iterables by way of the for-in statement).
The array de-structuring pattern can make short work of pulling values from an array that you want to assign to new variables. This pattern consumes iterables.
The spread operator takes an iterable and spreads out the values. I demonstrate the spreading of an array, string, and arguments in the code below.
Using yield*
with an iterable in a generator does exactly what you might think. It will loop over the iterable and add yield values.
let generator = function* () {
yield 1;
yield* [2,3,4]; //use an iterable, is looped, and added as yields
yield 5;
};
var iterator = generator();
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: 4, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
Now, that is really only three examples, but if you think about anything that accepts an array could potentially make use of the iteration protocol. For example, the following new ES6 constructors and methods accept iterables as arguments:
Array.from()
Map()
/WeakMap()
and Set()
/WeakSet()
Constructors (e.g. new Map([['a',1],['b',2]]
)Promise.all()
Promise.race()
It's exciting times to be a JavaScript programmer. And, I hope after reading this article you are excited by the fact that the new JavaScript has taken an evolutionary leap in terms of looping. I've only briefly touched on this topic in the article but I hope it's been enough to spark your interest and attention.
Header image courtesy of broombesoom
Cody Lindley is a front-end developer working as a developer advocate for Telerik focused on the Kendo UI tools. He lives in Boise, ID with his wife and three children. You can read more about Cody on his site or follow him on Twitter at @codylindley.