The JavaScript Looping Evolution

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

Comments

  • Pingback: Dew Drop – August 6, 2015 (#2068) | Morning Dew()

  • Super cool explained! Thank you! Its definitely a time to get my head around ES2015. Thank again!

  • Very cool .

  • burkeholland

    One of the things that I still can’t wrap my head around, is the use case for generators. So far, the only practical implementation I’ve seen is in Koa, and I don’t think that’s what generators were designed for.

    • mechrisreed

      Agreed, I am getting into functional js and keep thinking things like “why loop at all when you can recourse?”.
      Just like the lambda syntax solution in ES 2015. The problem was the call back mountain range and writing the word “function” all over the place. The solution in ES 2015 is smaller syntax. Where it should have been using composition so you don’t need to build the callback mountain in the first place.

  • What ECMAscript do that PHP don’t?

    • Csaba Toth

      It’s a client side language (and with Node.js it’s server side too). It’s the only language so far which all browsers understand and execute natively (not delving into the version details though)

      • So I can use PHP, just like ECMAscript? I mean… I could use PHP without use ECMA and make a rich web site?

        • Csaba Toth

          No, you cannot. Since PHP is server side only language, you can influence what happens on the client from server side only. You can do a lot from there, but for a rich web site you will need to use ECMAScript (a.k.a. JavaScript) on the client side for sure.

          • Thank you! This was a big question for me. You help a lot. Forgive my english. 😛

  • Csaba Toth

    The JS Bin examples run properly only on Chrome. Latest FF displays errors with some of the examples, Edge doesn’t output anything. I guess this is because of the new language constructs and the different level of adaptation of the new language features by the browsers?
    Very useful article though.

  • Pingback: Today’s Readings | Aaron T. Grogg()

  • I gave a talk recently on this topic: https://www.youtube.com/watch?v=eU2CQP2MJ6w covers a lot of similar stuff.

  • Pingback: Sunday is Link Day! Still Breathing - online home of Chris Taylor - web developer, musician, geek()

  • Pingback: FrontEnd courses list | webappexpress()