Telerik blogs
looping_header

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.

ES3 Looping

In ES3 several looping statements were available:

  • while loop
  • do-while loop
  • for loop
  • for-in loop.

Of 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.

ES5 Looping

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.

JavaScript 2015 Looping

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.

What's New?

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.

Values That Are loopable By Default (i.e built in iterables)

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 DOM
  • arguments
  • generator functions (Note: A generator is both an iterator and iterable)

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.

Looping a Array in ES6

JS Bin on jsbin.com

Looping a String in ES6

JS Bin on jsbin.com

Looping a Map in ES6

JS Bin on jsbin.com

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.

JS Bin on jsbin.com

Looping a Set in ES6

JS Bin on jsbin.com

Looping DOM nodes in ES6

JS Bin on jsbin.com

Looping arguments in ES6

JS Bin on jsbin.com

Looping generators in ES6

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:

  1. This is much easier than previous options for looping (and work with break, continue, and return).
  2. Values are automatically read until there are no more values.
  3. The iterating protocols are built into the aforementioned objects by default.
  4. Imagine using a generator and yield for looping over asynchronous operations until they are complete. Sweet!

Looping an Iterable Using An Iterator

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.

JS Bin on jsbin.com

Creating Your Own Iterable Objects

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.

JS Bin on jsbin.com

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.

Syntax Expecting iterables

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).

Array Destructuring

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.

JS Bin on jsbin.com

Spread operator

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.

JS Bin on jsbin.com

yield*

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:

JavaScript 2015 Offers an Evolutionary Iteration Leap

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
About the Author

Cody Lindley

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.

Comments

Comments are disabled in preview mode.