JavaScript for loops: ES5, ES6, ES2016 (ES7) and TypeScript

JavaScript for loops can use different syntax depending on the JavaScript flavor you're using: ES5 (ECMAScipt5), ES6 (ECMAScript6), ES2016 (ES7/ECMAScript7) or TypeScript. Up next you'll learn the differences between each for loop in terms of syntax, features and behaviors.

ES5: for loops

The JavaScript for loop you're most likely to encounter to this day uses an ES5 compliant syntax, which is actually part of the ES3 standard. Listing 4-1 illustrates two options of this classic JavaScript for loop syntax.

Listing 4-1. ES5: var scope behavior
// Data definition
var primeNumbers = [2,3,5,7,11];

// Option 1) Oldest way & least efficient, assigns length on each iteration and i hoisted to the top
var loopStartOpt1 = performance.now();
for (var i = 0; i < primeNumbers.length; i++) {
  console.log(primeNumbers[i]);
}
console.log( performance.now() -loopStartOpt1 + ' ms')

// Option 2) Improved loop, assigns length to variable & defines i to avoid hoisting
var dataLength = primeNumbers.length, i;

var loopStartOpt2 = performance.now();
for (i = 0; i < dataLength; i++) {
  console.log(primeNumbers[i]);
}
console.log(performance.now() - loopStartOpt2 + ' ms')

The first option for (var i = 0; i < primeNumbers.length; i++) is a staple of many JavaScript ES5 applications, but it's not very efficient. The problem with this first option is due to hoisting and the repeated calculation of a constant value. In this case, the i for loop control variable is declared inline, which forces the JavaScript engine to hoist the variable. In addition, an evaluation is made against primeNumbers.length on every iteration to determine whether to finish the loop, something that's inefficent for a value that doesn't change over the course of a loop.

The second option for (i = 0; i < dataLength; i++) produces a much more efficient for loop. Notice the i loop control variable is declared prior to defining the loop and a constant value dataLength is also created to evaluate when to stop the loop vs. invoking the length method on every iteration.

If you run the two for loop syntax variations in listing 4-1, the second option will always outperfom the first. This performance boost can be verified with the JavaScript performance.now() Web API[1] statements which times JavaScript operations in milliseconds.

Forget about the for/in loop syntax

For loops in ES5 -- technically since ES3 -- also support the for/in syntax[2]. The for/in syntax emerged as a way to iterate over object data types. However, because object data types can vary in their structure -- as you learned in the previous chapter on JavaScript data types (e.g. Array, Object (literal), Date) -- it makes the for/in syntax inadequate for things like Array object types, not only because for/in can execute in random order, but also because it can treat array properties (e.g. length) as part of a loop iteration.

ES5 introduced enhanced techniques to iterate over object properties that I'll mention shortly, so for practical purposes, you should avoid the for/in syntax. The only circumstances in which the for/in loop syntax is acceptable, is for loops involving object literals, however, even under these circumstances the for/in loop syntax is generally avoided in favor of newer loop syntax techniques.

There's really nothing ground-breaking about the two JavaScript for loop techniques in listing 4-1 -- except one is more efficient than the other. In fact, there's nothing ES5 specific about them either, as I mentioned, this for loop syntax has been part of JavaScript since ES3.

ES5: Arrays get iteration methods and Objects get keys

With the emergence of ES5, it became clear there was a need for more powerful loop constructs. However, it wasn't the for loop syntax that got a facelift, it was the Array data type that got iteration methods[3]. Array iteration methods in ES5 gave loops a whole new level of functionality. The iteration methods added in ES5 to Array data types were the following:

Of the previous array methods, the one you're most likely to have seen or used is the forEach() method. The forEach() method offers a quicker syntax to achieve the same results as the classic for loop syntax shown in the previous examples. Although the remainder of the array iteration methods are a little more esoteric in terms of functionality, they are also specifically designed to make for loop logic simpler and more powerful.

In addition to the Array data type getting its iteration methods, in ES5 the Object data type also got a method named keys()[4]. The purpose of Object.keys() is to return an array with an object's enumerable properties, just like -- you may have already guessed it -- the for/in loop which I already told you to forget about!

So with these new iteration features added to the various data types in ES5, JavaScript loops gained additional functionalites as shown in listing 4-2.

Listing 4-2. ES5: Array and Object iteration methods
// Data definition
var primeNumbers = [2,3,5,7,11];
var jsFramework = {'name':'React','creator':'Facebook','purpose':'UIs'}

// Option 1 ES5 forEach on array
var loopStartOpt1 = performance.now();
primeNumbers.forEach(function (value) {
  console.log(value);
});
console.log(performance.now() - loopStartOpt1 + ' ms')

// Option 2 ES5 forEach on Object
var loopStartOpt2 = performance.now();
Object.keys(jsFramework).forEach(function(property){
    console.log(property+' : '+jsFramework[property]);
});
console.log(performance.now() - loopStartOpt2 + ' ms')


// Test functions and data for other ES5 iteration methods
function isPrime(element, index, array) { 
  return element / 1; 
} 
function evenNumber(element,index,array) { 
  if (element % 2 === 0)
    return element;
}
function allCaps(str) {
    return str.toUpperCase()
}
var pageHits = ['home.html', 'react.html', 'home.html', 'angular.html', 'home.html','nodejs.html','react.html'];

function countPageHits(allPages, page) { 
  if (page in allPages) {
    allPages[page]++;
  }
  else {
    allPages[page] = 1;
  }
  return allPages;
}

//Other ES5 iteration method samples
console.log(primeNumbers.every(isPrime));//True
console.log(primeNumbers.every(evenNumber));//False
console.log(primeNumbers.filter(evenNumber));//[2]
console.log(primeNumbers.some(evenNumber));//True
console.log(Object.keys(jsFramework).map(allCaps));//[ 'NAME', 'CREATOR', 'PURPOSE' ]
console.log(pageHits.reduce(countPageHits,{}));
console.log(pageHits.reduceRight(countPageHits,{}));

The first option primeNumbers.forEach in listing 4-2 produces identical results as the previous classical for loop examples in listing 4-1, except it does using a much simpler syntax -- notice the lack of a control variable and constant to determine when to end a loop. The forEach method walks through every element in an array, where the function() defines the logic to apply to each array element.

The second option Object.keys(jsFramework).forEach also illustrates the use of the forEach array method, but notice how the method is applied on a resulting array from jsFramework via the Object.keys method. This syntax effectively allows us to forgo use of the for/in to extract the values of a JavaScript Object.

The remaining examples in listing 4-2 showcase the other ES5 iteration methods designed to loop over arrays.

Performance wise, ES5 iteration methods don't offer any noticable performance boost over classic ES3 for loops, the only difference is ES5 iteration methods make many common loop operations easier to write. However, although ES5 array iteration methods offer the advantage of more succint syntax, they do come with some drawbacks.

The first drawback of using array iterations methods in loops, is there's no way to short-circuit a loop (i.e. there's no support for the break keyword, unlike classical for loops). The second drawback is all methods are part of the Array data type, which means that in order to perform a loop over non-arrays, you must first convert a non-array data structure to an Array. Although this last process is fairly straigthforward with an Object -- which has the keys method to produce an array -- it can cause a lot of conversion overhead for less common or custom JavaScript data types (e.g. a DOM NodeList or HTMLCollection).

ES6: for loops

With the increasing demands placed on the JavaScript language (e.g. data intensive JavaScript frameworks, JavaScript running on the server) JavaScript language designers re-addressed JavaScript's for loop shortcomings in ES6. ES6 tackled for loops in the following fronts:

ES6: Iterable & iterator protocols and the for...of syntax

The iterable and iterator protocols[5] are among the most important additions to ES6 and they're directly tied to the behavior of for loops. The term protocol stems from the fact iterable and iterator are conventions followed by JavaScript data structures. Like all protocols, they can either be followed or ignored, so a JavaScript data structure may or may not support the iterable and iterator protocols (e.g. an ES5 Array isn't an iterable, but an ES6 Array is an iterable). In ES6, almost all JavaScript data structures are -- or can become -- iterables.

So what is an iterable ? It's a data structure that has elements that can be looped over. An iterable can be as simple as a string (e.g. looping over a string to get each letter) or an array, to something as elaborate as a data structure representing a file (e.g. looping over each line in a file).

An iterable represents a more powerful approach to looping over data structures, because iterables work in conjuction with iterators. Iterators represent the mechanism to move over iterables. In most cases, an iterator walks through all the elements in an iterable, but it's possible to customize or terminate an iterator prematurely just like it's done in regular loops (e.g. using the break keyword).

In order to work with iterables and iterators, the ES6 standard also introduced the for...of syntax, which is specifically designed to loop over iterables. The general syntax to create a loop over an iterable with the for...of syntax is the following:

for (let <reference> of <iterable_data_type>) {
    console.log(<reference>);
}

As you can see, for...of has a pretty straightforward syntax. But before you completely forget about the classic JavaScript for loop syntax and drop it in favor of for...of, remember for...of only works on iterable data types. Listing 4-3 illustrates the use of for...of on iterables.

Listing 4-3. ES6: for...of with iterators
// Data definition
let primeNumbers = [2,3,5,7,11];
let jsFramework =  new Map([['name','React'],['creator','Facebook'],['purpose','UIs']])

// Option 1 ES6 for..of on array which is iterable
let loopStartOpt1 = performance.now();

for (let value of primeNumbers) {
    console.log(value);
}
console.log(performance.now() - loopStartOpt1 + ' ms')

// Option 2 ES6 for..of on Map which is iterable
let loopStartOpt2 = performance.now();
for (let [key, value] of jsFramework){
    console.log(key+' : '+ value);
}
console.log(performance.now() - loopStartOpt2 + ' ms')

// Manually progress over iterable with next()
// Create array from Map keys 
let characteristics = jsFramework.keys();
console.log(characteristics.next().value);
console.log(characteristics.next().value);
console.log(characteristics.next().value);
// Output return value of next() confirming iterable reached end
console.log(characteristics.next());

The first option uses the same primeNumbers array from earlier examples, but in this case, it uses the for...of syntax to loop over each array element. The second option uses a Map data type -- introduced in ES6 data types -- instead of the generic Object data type from earlier examples. The reason Map is used instead Object, is because an Object data type isn't an iterable -- but more on this in a second.

Notice the second option for (let [key, value] of jsFramework) declares a dual array instead of a single reference, this is due to the way Map data types group key-value pairs in a similar fashion to Object data types. The loop iterates over each of the Map key-values and you can use the reference array (i.e.[key, value]) to access the distinct values.

A 'behind the scenes' behavior of the for...of loop is that it progressively advances over the elements in an iterable, changing the reference value(s) on each iteration. This happens because iterable data types trigger a call to their next() method as they progress through the for...of loop, all of which takes us to the last sample shown in this last set in listing 4-3.

The last sample in listing 4-3 generates an array from the jsFramework map using the Map.keys() method. But instead of using a for...of loop on it, the array uses explicit iterable methods. Notice how the next() method is called multiple times on the array. The next() method effectively tells the iterable data type move me to the next element -- just like the for...of loop does implicitly -- except in this case you explicitly control when an iterable moves to its next element. Because an iterable uses the next() method to progress over the underlying data structure, it not only means that you don't require an explicit for loop, it also means an iterable has the built-in mechanism to detect when it reaches its end -- notice the lack of a length check or any other comparison operation to determine when to end a loop like its done in classical JavaScript for loops.

The next() method returns an object with the done and value properties, where the former indicates if the iterable is done (i.e. there are no more iterable elements) and the latter returns the value of the current iteration. This is why value is chained to the next() method, to output the value of the current iteration. Toward the end of the last sample, you can see the final call characteristics.next() outputs { value: undefined, done: true } which indicates the next value in the iterable is undefined and the iterable as a whole is done. This same mechanism is how for...of loops determine when they need to finish.

ES6: Revamped and new data types with new iteration methods

ES6 revamped older data types (e.g. String, Array) and introduced new data types (e.g. Map, Set) to support the iterable protocol and the iterator protocols. However, not all data types automatically became iterables in ES6. For example, the general purpose Object data type mainly due to its ample and legacy scope is not an iterator[6]. But you can implement an iterator on an Object if you really need to[7], albeit using keys() on an Object -- is equally valid in ES6 as it's in ES5 -- to net you an Array that's an iterable.

But although the JavaScript Object data type didn't get much attention in ES6 in terms of for loops -- it would have to wait until ES7 -- the popular JavaScript Array data type did gain additional iteration methods. The iteration methods added to Array in ES6 were the following:

As you can see in this past list, some of these methods are directly tied to the addition of iterable/iterator protocols, but also notice some other methods are aimed at simplifying common for loop logic, like the iteration methods added in ES5.

ES6: Generators and yield expressions

A problem with for loops made against large data structures -- hundreds or thousands of items -- is efficiently processing them in memory. If you consider looping over large data structures is often associated with sequential operations (e.g. 1,2,3,4,5,6,7...) an Array or Object data type per se -- even as an iterable -- they represent a weak choice for large data structures. The introduction of iterables and iterators in ES6, also gave way to generators and yield expression which are designed to tackle this issue.

Generators are iterators embodied as functions. In a standard function you call the function and it runs uninterrupted until it finds return. In a generator function -- which are simply called generators -- you can integrate pauses into a function so that each time it's called, it's a continuation of the previous call. In addition, generators have the ability to generate values on-demand -- which is why they get their name -- so they're much more efficient when it comes to handling large data ranges.

To create a generator you append the * to a function declaration, as illustrated in the following snippet:

function* myGenerator() { 
  yield 2;
  yield 3;
  yield 5;
}

let g = myGenerator();
console.log(g.next().value) // outputs 2
console.log(g.next().value) // outputs 3
console.log(g.next().value) // outputs 5

The myGenerator() generator is unconventional and is intended to illustrate the basic use of yield expressions with the yield keyword. The yield keyword works as a combination of return & stop behavior. Notice the generator is assigned to the g reference, once this is done, you can start stepping through the generator with the iterable next() method -- after all, a generator is an iterator.

On the first next() call, the generator gets to yield 2, where it returns 2 and stops until next() is called again. On the second next() call the generator gets to yield 3, it returns 3 and stops until next() is called again. This process goes on until the last yield is reached and the next() method return { value: undefined, done: true } just like all iterators do.

With this initial overview of generators and the purpose of the yield keyword, let's analyze a few real-life scenarios that can benefit from these techniques.

Listing 4-4 illustrates a more realistic example of JavaScript generators, where the generator is designed to return a sequence of prime numbers with the potential to return an infinite amount of prime numbers with minimal memory consumption (i.e. the prime numbers don't need to be hard-coded, they're generated on-demand).

Listing 4-4. ES6 : Generators
// Generator to calculate prime numbers
function* primeNumbers() { 
  let n = 2;

  while (true) {
    if (isPrime(n)) yield n;
    n++;
  }

  function isPrime(num) {
    for (let i = 2; i <= Math.sqrt(num); i++) {
      if (num % i === 0) {
        return false;
      }
    }
    return true;
  }
}

// Create generator
let primeGenerator = primeNumbers();
// Advance through generator with next(), print out value
console.log(primeGenerator.next().value);
console.log(primeGenerator.next().value);
console.log(primeGenerator.next().value);
console.log(primeGenerator.next().value);
console.log(primeGenerator.next().value);
// Calls to primerGenerator.next() returns infinite prime numbers

Notice how the primerNumbers generator function in listing 4-4 doesn't declare a hard-coded prime numbers array like the previous examples. Internally, the primerNumbers generator starts with a value of n=2 and increases this value through an endless loop (i.e.while(true)) yielding a result each time n evaluates to a prime number via the logic in isPrime.

Next, you can see the primerNumbers generator is initialized and multiple calls are made to the next() method to advance through the generator/iterator. Finally, value is extracted from each next() method result to output the given prime number in the iteration.

By using this technique, you effectively generate prime numbers as they're needed, instead of declaring and loading them in a single step. This is particularly helpful and more efficient for cases where there's a potential for hundreds or thousands of elements.

ES2016 (ES7): for loops

Although ES6 represented some of the biggest changes in for loops since since JavaScript's inception, this didn't mean ES2016 (ES7) remained static in the for loop front. In ES2016 (ES7), the Object data type regained the attention of language designers getting two additional methods -- values()[8] and entries()[9] -- aimed at working with arrays which ties into the use of for loops and who's behavior is illustrated in listing 4-5.

Listing 4-5. ES2016 (ES7): Object methods
let jsFramework = {'name':'React','creator':'Facebook','purpose':'UIs'}

// ES7 Object.values in Array
for (const val of Object.values(jsFramework)) {
    console.log(val);
}

// ES7 Object.entries into Array
for (const [key,value] of Object.entries(jsFramework)) {
    console.log(key+' : '+ value);
}

As you can see in listing 4-5, in ES2106 (ES7) you can directly use the Object data type to produce an Array and use the results in a for...of loop given an Array is an iterable. The Object.values() method produces the values of an Object in an array, complementing the Object.keys() method which had been available since ES5 to produce an array of Object keys. The Object.entries() method converts an Object into an array of two-value key-value pairs, where each key-value pair represents an Object property-value.

TypeScript: for loops

TypeScript loop syntax doesn't suffer from the same tortuous evolution as ECMAScript, mainly because TypeScript came to light when ES6 was close to completion. This means that from a feature standpoint, TypeScript loop syntax parts from the same foundations as ES6 (e.g. it supports iterators and generators out-of-the-box). Inclusively, even if a TypeScript loop is transpiled into an older ES6 JavaScript flavor (i.e. ES3, ES5), TypeScript generates synthetic array iterators to emulate ES6-like behavior[10]. In this sense, TypeScript loop syntax is truly portable across any ECMAScript version.

  1. https://developer.mozilla.org/en-US/docs/Web/API/Performance/now    

  2. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in    

  3. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Iteration_methods    

  4. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys    

  5. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols    

  6. http://stackoverflow.com/questions/29886552/why-are-objects-not-iterable-in-javascript    

  7. http://stackoverflow.com/questions/29885220/using-objects-in-for-of-loops    

  8. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values    

  9. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries    

  10. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#new---downleveliteration