JavaScript for loops

Loops are a pretty common concept in programming languages, where you inspect the contents of a data structure to modify its pre-existing form or create new data structures. JavaScript, like other programming languages has many ways to loop over data structures, depending on what you plan to do with the data inside it, the size of a data structure, as well as the type of data strucutre. In this chapter you'll learn the various ways to loop over JavaScript data structures, which have been enhanced throghought various ECMAScript versions.

The for statement: Explicit counters, iterations and step sizes, the early years  

The earliest JavaScript for loop you're likely to encounter to this day is one with a for statement. The reason the for statement is still in use today, is because it's the most flexible, with the ability to prematurely finish a loop based on any condition, define iterations by something other than a data structure's size, as well as adjust the increments in which iterations are made (a.k.a. step size).

The syntax of the for statement consists of three expressions separated by ; wrapped in () and prefixed with for, with the logic to execute on each iteration wrapped in a block statement by curly brackets {}. The purpose of the three expressions that follow the for statement is the following:

  1. Initialization expression for the loop. Generally used to declare a loop control variable (e.g.let i = 0;).
  2. Expression evaluated at the start of every loop iteration, to determine to execute the block statement when true or finish the loop when false. Generally used with control variable & limit to determine amount of iterations (e.g. i < 10;).
  3. Expression evaluated at the end of every loop iteration. Generally used to increase or decrease loop control variable (e.g.i++;)

Due to the flexibility of the for statement, strictly speaking all three expressions that accompany a for statement are optional. However, although it's valid to declare a for loop in the form for(;;) { } -- which is an endless loop -- and rely on a statement inside the block statement to terminate it, this type of for syntax without expressions can be non-obvious and confusing, not to mention there are other more obvious JavaScript for loop syntax alternatives to declare endless loops, such as the the while and do while statements.

Although the expressions to control a for statement can appear to be fail-safe, the manner in which these expressions are declared can have an impact on performance. This impact varies when declaring the control variable with the var keyword or the let keyword -- because it influences scope visibility and hoisting -- as well as if the number of iterations are specified as a constant or calculated on every iteration.

Listing 6-1 illustrates four variations of this classic JavaScript for loop syntax with performance metrics using the console object's console.time() & console.timeEnd() methods.

Listing 6-1. For loops: Explicit counters, iterations & step sizes with performance
let primeNumbers = [2,3,5,7,11];

// Option 1) Least efficient
// var as counter & size in loop definition
console.time("loopOption1");
for (var i = 0; i < primeNumbers.length; i++) {
  console.log(primeNumbers[i]);
}
console.timeEnd("loopOption1");

// Option 2) More efficient
// var predefined with counter & size
var primeLength = primeNumbers.length;

console.time("loopOption2");
for (var j = 0; j < primeLength; j++) {
  console.log(primeNumbers[j]);
}
console.timeEnd("loopOption2");

// Option 3) More efficient
// let as count & size in loop definition
console.time("loopOption3");
for (let k = 0; k < primeNumbers.length; k++) {
  console.log(primeNumbers[k]);
}
console.timeEnd("loopOption3");

// Option 4) Most efficient
// let as count & size in predefined const
const primeLengthConst = primeNumbers.length;

console.time("loopOption4");
for (let l = 0; l < primeLengthConst; l++) {
  console.log(primeNumbers[l]);
}
console.timeEnd("loopOption4");

The first option and least efficient syntax presented in listing 6-1 consists of declaring the control variable with the var keyword and declaring the number of iterations directly with .length property of the data structure. 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 when to finish the loop, something that's inefficient for a value that doesn't change over the course of a loop.

The second option in listing 6-1 produces a slightly more efficient for loop by declaring the number of iterations in a predefined variable primeLength vs. calling the .length property on the data structure on every iteration. The third option produces an even more efficient for loop by declaring the counter variable with the let keyword -- to avoid hoisting -- although it still uses the .length property on the data structure on every iteration.

The fourth option in listing 6-1 which produces the most efficient for loop, uses the let keyword to declare the control variable and declares the number of iterations in a predefined variable primeLengthConst as a const vs. calling the .length property on the data structure on every iteration. As you can see from these examples, the biggest performance impact in this kind of for loop is in the control variable delivering more performance with the let keyword vs. var, followed by declaring the number of iterations in a predefined variable with const vs. calculating the number of iterations every time with the .length property of a data structure.

The break and continue statements: Exiting for loops and iterations prematurely

For loops in general or any given iteration in a for loop sometimes need to be exited before they fulfill their intended workload. In JavaScript, the continue statement is used to exit a foor loop iteration before it concludes, whereas the break statement is used to exit a for loop entirely before it concludes. It's worth pointing out the break and continue statements are applicable to any JavaScript for loop and not just loops created with the for statement presented earlier.

Listing 6-2 illustrates the use of the break and continue statements in for loops.

Listing 6-2. For loops: break and continue statements
let primeNumbers = [2,3,5,7,11];

const primeLength = primeNumbers.length;

for (let i = 0; i < primeLength; i++) {
  // Continue to next iteration when primeNumber > 4 & < 10
  if (primeNumbers[i] > 4 && primeNumbers[i] < 10) {
     continue;
  }
  console.log(primeNumbers[i]);
}


let vowels = ["a","e","i","o","u"];

const vowelsLength = vowels.length;
console.log("vowels ",vowelsLength);

for (let j = vowelsLength-1; j >= 0; j--) {
  // Break loop when vowel == "i"
  if (vowels[j] == "i") {
     break;
  }
  console.log(vowels[j]);
}

The first example in listing 6-2 loops over the primeNumbers array and inside the block statement checks if the value of the iteration on primeNumbers is larger than 4 or lower than 10 to invoke a continue statement. In this case, when a primeNumbers value is between 4 and 10 the continue statement causes the loop to immediately move to the next iteration, skipping the log statement when the primeNumbers iteration value is 5 and 7.

The second example in listing 6-2 loops over the vowels array with a decreasing counter to facilitate a reverse order loop. Inside the block statement, notice a check is made for when the value of the iteration on vowels equals "i" to invoke a break statement. In this case, when a vowels value equals "i" the break statement causes the loop to finish immediately, skipping the log statement when the vowels iteration value equals "i", as well as any remaining iterations (i.e. when a vowels value is "e" and "a").

Label statements in for loops: GoTo a specific point in the workflow

JavaScript labels support the GoTo[1] programming construct. Although GoTo's are often controversial in many programming languages since they can reduce readability and lead to "spaghetti" workflows, GoTo's serve to change the execution workflow of a program to a predetermined line of code.

In the context of for loops, labels can be particularly helpful to change the execution workflow in nested loops. While the break and continue statements are used to completely exit a loop or prematurely exit a loop iteration, they do so in the context of a single loop. In order to exit loop iterations in either form when more than one loop is involved, label statements are used in conjunction with the break and continue statements to indicate to what line of code to break or continue a workflow.

Label names in JavaScript are declared as words followed by a colon : to indicate a potential location where workflow control can be sent, which means they're typically declared right before the start of a for loop. In order to send workflow control to a given label, the break and continue statements are used in conjunction with a label's name, where a break or continue statement is followed by a label name. It's worth pointing out label statements are applicable to any JavaScript for loop and not just loops created with the for statement presented earlier.

Listing 6-3 illustrates the use of labels in for loops.

Listing 6-3. For loops with labels
let primeNumbers = [2,3,5,7,11];

const primeLength = primeNumbers.length;

// Outer label
xPlane:
for (let x = 0; x < primeLength; x++) {
  // Inner label
  yPlane:
  for (let y = 0; y < primeLength; y++) {
     console.log("Processing ", primeNumbers[x], primeNumbers[y]);
     if (primeNumbers[x] == primeNumbers[y]) { 
      console.log("Symmetrical coordinate ", primeNumbers[x], primeNumbers[y]);
      // Continue to outer label after symmetrical match
      continue xPlane;
     }
  }
}


let vowels = ["a","e","i","o","u"];

const vowelsLength = vowels.length;

// Placeholder array to use inside loop
let diphthongs = [];

// Outer loop
outerLoop:
for (let i = 0; i < vowelsLength; i++) {
  // Inner loop
  innerLoop:
  for (let j = 0; j < vowelsLength; j++) {
    // Continue to inner loop if vowels are the same
    if (vowels[i] == vowels[j]) { 
      continue innerLoop;
    }
    // Add up to ten diphthongs to diphthongs array 
    if (diphthongs.length < 10) { 
      diphthongs.push(`${vowels[i]}${vowels[j]}`);
    // Break to outer label after ten diphthongs
    } else { 
      break outerLoop;
    }
  }
}
// Output all diphthongs added to diphthongs array
console.log("First ten diphthongs ", diphthongs);

The first example in listing 6-3 creates a pair of loops to iterate over the primeNumbers array to output coordinates. Notice that preceding each for statement are the labels xPlane: and yPlane:. When the values for each iteration in primeNumbers are the same, the symmetrical coordinate is logged and the continue xPlane; statement is used to send the workflow back to the start of the outer loop and avoid making unnecessary iterations over values that aren't symmetrical. The log statement console.log("Processing ", primeNumbers[x], primeNumbers[y]); confirms the continue xPlane; statement works, since coordinates where values of the outer loop are larger than the inner loop are never processed.

The second example in listing 6-3 creates a pair of loops to iterate over the vowels array to place diphthongs in a separate array. Notice that preceding each for statement are the labels outerLoop: and innerLoop:. When the values for each iteration in vowels are the same, the continue innerLoop; statement is used which has the effect of a plain continue statement because it's applied to the inner most loop. If the values for each iteration in vowels are the same, a diphthong is created with the values and added to the diphthongs array. Once the tenth diphthong is added to the diphthongs array, the loop enters the break outerLoop statement which is used to send the workflow to the outer loop and terminate the loop. Finally, you can see the diphthongs array is logged with ten diphthong elements in it.

The while and do while statements: Loops that run with one expression  

An alternative to loops with for statements, are for loops with while or do while statements. Unlike for statements which typically use up to three expressions to define a loop's logic, loops with while and do while statements only require one expression to define when a loop ends. This makes while and do while statements much simpler to declare, but at the same time it can make them less flexible since any control variable and/or step size adjustment must be made directly in a loop's body.Therefore, for loops with while or do while statements are best suited when there's no need for a control variable or step size adjustment.

The syntax of the while and do while statements consists of an expression to end a loop when it evaluates to false, as well as a block statement wrapped in curly brackets {} that contains the logic to execute on every iteration the loop's expression evaluates to true. The only difference between a for loop with a while and do while statement, is the while statement evaluates its expression before it performs an iteration and the do while statement evaluates its expression after an iteration is done. This guarantees a do while for loop runs its block statement at least once and always before a determination is made to finish the loop, while a while for loop iteration only runs its block statement after it's determined if a loop's work is complete.

For loops with the while and do while statements can also use break and continue statements to exit loops or iterations prematurely, as well as label statements to alter the workflow of a loop and send it to a predetermined line of code. In addition, it's also possible to create a cleaner endless loop with a while or do while statement using the syntax while(true) { } or do { } while(true) syntax and relying on a break statement inside the loop to terminate it.

Listing 6-4 illustrates JavaScript how to create for loops with the while and do while statements.

Listing 6-4. For loops with while and do while statements
let primeNumbers = [2,3,5,7,11];

while (primeNumbers.length) { 
  console.log(primeNumbers.pop());
}

let vowels = ["a","e","i","o","u"];

do { 
  console.log(vowels.shift());
} while (vowels.length > 2);

The while loop in listing 6-4 iterates over the primeNumbers array. The expression of the while loop checks primeNumbers.length, therefore it always does an iteration so long as the primeNumbers array is not empty. Inside the block statement of the while loop, the Array data type's pop() method is used to remove the last element from primeNumbers and output said element in a log statement, in this manner, the array eventually empties causing the while loop expression to be evaluated to false and finish the loop.

The do while loop in listing 6-4 iterates over the vowels array. The expression of the while loop checks for vowels.length > 2, therefore an iteration is made so long as the vowels array has more than elements. Inside the block statement of the do while loop, the Array data type's shift() method is used to remove the first element from vowels and output said element in a log statement, in this manner, the array eventually reaches a size of 2 causing the do while loop expression to be evaluated to false and finish the loop.

The for in statement: A loop for key-value data structures, the early years  

Note The for in statement is a dated loop syntax. Since its creation, better alternatives have surfaced, including newer loop syntax and data type enhancements to support loops.

The for in loop syntax was the first construct designed to loop over JavaScript key-value data structures. Unlike the for, while and do while statements which don't necessarily peg loops to data structures, that is, they can run loops with 'n' iterations using counters and conditionals irrespective of a data structure, the for in statement requires loops to be made against a data structure.

The syntax of the for in statement consists of a key-value data structure declared after the in keyword to loop over each element in the data structure. In between the for and in keywords is a variable to hold the key of each element on every iteration. After the in keyword, a block statement in curly brackets {} is declared to execute business logic on each iteration which has access to the variable declared in between the for and in keywords. Like other JavaScript loops, the block statement of a for in statement can also make use of break and continue statements -- including label statements -- to exit a loop or iteration prematurely.

The for in loop is designed to iterate over key-value data structures using the following rules:

To fully grasp what these last two rules mean for a for in loop, it's inevitable to take a closer look at JavaScript data types that have key-value data structures.

The most versatile key-value data structure in JavaScript is the Object data type and it's precisely this data structure that the for in loop is more suited to, as well as the data structure where you can potentially face these two rules associated with for in loops. Now, this doesn't mean the Object data type is the only key-value data structure in JavaScript, other data types like the Array data type & the String data type also operate as key-value data structures, so they too can be run through a for in loop.

However, because data types like Array and String always have elements with simple key integer values and lack the concept of element values being enumerable -- not to mention these data types have other built-in loop mechanisms -- the for in statement is almost always used on Object data types.

When it comes to the Object data type and two for in rules mentioned in the prior list, you really need to go out of you way to face them. Most keys added to Object data types aren't Symbol's and most values added to Object data types are enumerable by default. To make the key of an Object data type a Symbol you need to explicitly create a Symbol data type and to make the value of an Object data type non-enumerable you must explicitly set its enumerable property descriptor to false, which can only be done with the Object.defineProperty() or Object.defineProperties() methods.

Listing 6-5 illustrates the use of the for in statement on Object data types and for the sake of completeness also shows how the for in statement can be used on String and Array data types.

Listing 6-5. For loops: for in syntax
let language = "JavaScript";
let vowels = ["a","e","i","o","u"];
let languages = {JavaScript: {
                  creator:"Eleanor Eich"
                  }
                }

// Loop over String
for (const key in language) { 
  console.log(`langauge 
  key: ${key};
  value: ${language[key]}`);
}

// Loop over Array
for (const key in vowels) { 
  console.log(`vowels
  key: ${key};
  value: ${vowels[key]}`);
}

// Loop over Object
for (const lang in languages) {
  console.log(`languages
  key: ${lang};
  value: ${JSON.stringify(languages[lang])}`);
}

// Loop over nested Object property
for (const version in languages.JavaScript) { 
  console.log(`languages.JavaScript
  key: ${version};
  value:${languages.JavaScript[version]}`);
}

// Add properties to object in various forms
// Dot notation
languages.TypeScript = {creator:"Microsoft Corporation"};
// Object.defineProperty as not enumerable
Object.defineProperty(languages, "Ruby", {
  value: {creator:"Yukihiro Matsumoto"},
  enumerable: false
});
// Object.defineProperty as enumerable 
Object.defineProperty(languages, "Rust", {
  value: {creator:"Graydon Hoare"},
  enumerable: true
});
// Bracket notation
languages["WebAssembly"] = {creator:"World Wide Web Consortium (W3C)"};
// Bracket notation, with Symbol key
languages[Symbol.for("Python")] = {creator:"Guido van Rossum"};

// Inspect languages object 
// All properties are there and output
console.log("languages: %o", languages);


// Loop over languages object
// Non-enumerable values ("Ruby") and Symbol keys ("TypeScript") are ignored by for in
for (const lang in languages) { 
  console.log(`languages
  key: ${lang}; 
  value: ${JSON.stringify(languages[lang])}`);
}

Listing 6-5 starts by declaring three data types: a String, an Array and an Object. Next, you can see for in loop are applied to each one. In each case, notice the variable declared in between the for and in keywords is used inside the loop's block statement, to output a log message with the key and also use the key to access its associated value.

The for in loop output for String language and Array vowels confirms the prior statement about keys in these kind of data structures, all keys are integers. In addition, notice the associated values for each key constitute either a String character or an Array element. The Object languages then has two for in loop applied to it, the first one loops over the entire language object, while the second loop uses the JavaScript key to loop over the nested object languages.JavaScript.

The more interesting aspect in listing 6-5 is the section where various elements are added to Object languages. First, a dot notation is used to add the TypeScript key with a value of {creator:"Microsoft Corporation"};. Next, two more elements are added to languages with the Object.defineProperty() method: the "Ruby" key with a property descriptor enumerable: false and a value of {creator:"Yukihiro Matsumoto"}; and the "Rust" key with a property descriptor enumerable: true and a value of {creator:"Graydon Hoare"}. Finally, two additional elements are added to languages with a bracket notation: the "WebAssembly" key with a value of {creator:"World Wide Web Consortium (W3C)"}; and the Symbol key Symbol.for("Python") with a value of {creator:"Guido van Rossum"}.

Once the new elements are added to Object languages, a plain console object log statement is used to output the contents in languages to confirm all new elements are present. However, the last statement in listing 6-5 performs a for in loop over the updated languages object where you can see the elements with the "Ruby" key and Symbol.for("Python") key aren't output. The reason these elements aren't output, is because the "Ruby" key value is marked with a property descriptor of enumerable: false and the key Symbol.for("Python") is a Symbol, both of which are skipped in for in loops.

Array data type iteration methods, baked-in for loops for specific tasks  

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.

Object data type keys() method: The beginning of the end for the for in statement  

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.

  1. https://en.wikipedia.org/wiki/Goto    

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

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

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

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

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

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

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