JavaScript for loops

JavaScript 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:

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 when a condition is true, define the number of times a loop runs 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 block of code 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 parameters. 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 block 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 with for statements and performance metrics
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 times the loop runs directly with the .length property of the data structure. The problem with this first option is due to block scope & hoisting it leads to 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. In the third option, the loop is executed even more efficiently 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 times the loop runs 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  

Often times a loop is executed until a certain condition evaluates to true, exiting its workload prematurely. 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 type of 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 with 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 of code 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 of code, 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 condition evaluates to true and 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 type of 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 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, 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.

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 loops with the while and do while statements.

Listing 6-4. 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 with for in statements
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.

The Array data type baked-in for loops, methods with callbacks: every(), filter(), forEach(), map(), reduce() & reduceRight()  

All the previous for loop statement variations -- for, while, do while and inclusively for in -- are capable of iterating over an Array data structure, however, the use of control variables and termination expressions can be tedious to write, specially if all you want to do is iterate over each item in the Array and be done with it. To that end, the Array data type supports a series of methods specifically designed to simplify how a loop is executed over each item in the Array data structure.

The following is a list of iteration methods available in the Array data type since ECMAScript 5 (ES5):

These Array methods that simplify for loops work with callback functions. This means each of these Array methods accepts another function as an argument (a.k.a. callback function) that gets run on each of the elements in an Array. It's just like placing the logic of a block statement in a for loop statement (e.g. for, while, do while) inside a function, to run on each iteration, without worrying about control variables or termination expressions.

Although these Array methods can produce the same outcome as the for loop statements described earlier, they do operate with the following differences:

Of all the Array methods that simplify for loops, the one you're most likely to use is the JavaScript forEach() method. The forEach() method makes no assumptions about what logic to apply to elements, therefore it's the most generic Array method to simplify for loops and resembles the for loop statements shown in the previous examples.

Listing 6-6 contains examples of the Array forEach() method, including small syntax variations that use plain functions and arrow functions, as well as a return statement to prematurely exit iterations.

Listing 6-6. Array forEach() method
let primeNumbers = [2,3,5,7,11];

// Array forEach()
primeNumbers.forEach(function (element, index, array) {
  console.log(element);
  console.log("index:", index);
  console.log("array:", array);
});

// Equivalent with ES6 arrow function
primeNumbers.forEach((element, index, array) => {
  console.log(element);
  console.log("index:", index);
  console.log("array:", array);
});

// Array forEach() with return
primeNumbers.forEach(function (element) {
  // Move to next iteration when element > 4 & < 10
  if (element > 4 && element < 10) {
    return;
  }
  console.log(element);
});

// Equivalent with ES6 arrow function
primeNumbers.forEach(element => {
  if (element > 4 && element < 10) {
    return;
  }
  console.log(element);
})

The first forEach() example in listing 6-6 loops over all the elements in the primeNumbers array, just like it's done in the examples in listing 6-1 with a for statement. Notice the callback function uses the arguments element, index & array to access each of these value on every iteration and output them with a log statement. The second forEach() example uses an arrow function to simplify the callback function syntax and produce the same outcome. Because arrow functions were introduced in ECMAScript 6 (ES2015) -- after the Array forEach() method -- there's a small time gap where the forEach() method only worked with inline callback functions, so both alternatives are valid, albeit arrow functions are the modern approach.

The third and fourth forEach() examples in listing 6-6 make use of a return statement, so in case a primeNumbers value is between 4 and 10, the function immediately concludes and the next iteration starts. This behavior is like the example in listing 6-2 with a for and continue statement. In addition, notice these last callbacks only use the single argument element vs. three arguments like the first two examples in listing 6-6. The only difference between the third and fourth examples, is the third example uses an inline callback function, while the fourth example uses an equivalent arrow callback function.

As outlined in the iteration method list, the other Array methods that simplify for loops are designed to fulfill more specific tasks. Listing 6-7 illustrates examples for each of the methods.

Listing 6-7. Array other for loop methods: every(), filter(), map(), reduce(), reduceRight(), some()
let primeNumbers = [2,3,5,7,11];
let pageHits = ["index.html", "syntax.html", "index.html", "for-loops.html", "index.html","asynchronous.html","syntax.html"];

let primeNumbers = [2,3,5,7,11];
let pageHits = ["index.html", "syntax.html", "index.html", "for-loops.html", "index.html","asynchronous.html","syntax.html"];

// Start suport functions for iteration methods
function isPrime(element, index, array) {
  return element / 1; 
}

function evenNumber(element, index, array) { 
  if (element % 2 === 0)
    return element;
}

// Custom function for ES5
// can be replaced with ES6 arrow function: element => element.toUpperCase() 
function allCaps(element, index, array) {
    return element.toUpperCase()
}

function countPageHits(allPages, page) { 
  if (page in allPages) {
    allPages[page]++;
  }
  else {
    allPages[page] = 1;
  }
  return allPages;
}
// End support functions for iteration methods

// Array every()
console.log(primeNumbers.every(isPrime));//true
console.log(primeNumbers.every(evenNumber));//false

// Array filter()
console.log(primeNumbers.filter(evenNumber));//[2]

// Array map()
// ES5 with custom function
console.log(pageHits.map(allCaps));
// ES6 with array function
console.log(pageHits.map(element => element.toUpperCase()));

// Array reduce()
console.log(pageHits.reduce(countPageHits,{}));

// Array reduceRight()
console.log(pageHits.reduceRight(countPageHits,{}));

// Array some()
console.log(primeNumbers.some(evenNumber));//true

The first half of listing 6-7 declares a series of support functions to use as callback functions on each of the Array functions that simplify for loops. These support functions include: isPrime() which determines if a number is prime; evenNumber() which determines in a number is even; allCaps() which returns the value of a string in upper case letters; as well as countPageHits() which counts the number of occurrences of an element in a given array.

Next, you can see in listing 6-7 how the Array every() method is used to evaluate if all the elements in the primeNumbers array are prime and odd, leveraging the isPrime() and evenNumber() callback functions, respectively. You can see primeNumbers.every(isPrime) returns true and primeNumbers.every(evenNumber) returns false. Then the Array filter() method is used with the evenNumber() callback function to get all the even numbers in the primeNumbers array, in this case, you can see the result is [2].

Next, you can see in listing 6-7 two calls made on the pageHits array with the Array map() method to apply a function to all the elements in an array. The two calls made with the map() method use different syntax and callback functions. The first map() call uses the support function allCaps() also in listing 6-7 to turn every element in the pageHits array to upper case, while the second map() call uses an arrow function to directly invoke the String data type upperCase() method to convert every element in the pageHits array to upper case.

The purpose of the Array reduce() and reduceRight() method calls in listing 6-7 is to apply a reducer function[2] -- in this case countPageHits() -- to obtain the sum of all the elements in an array, in this case, the pageHits array. The only difference between reduce() and reduceRight is the order in which the reducer function is applied to the array, with reduce() applying the reducer function from left (start of an array) to right (end of an array) and reduceRight() applying it from right (end of an array) to left (start of an array). This is the reason applying reduce() to the pageHits array results in the keys being ordered from first to last appearance in the array (e.g. {'index.html': 3, 'syntax.html': 2, 'for-loops.html': 1, 'asynchronous.html': 1}) and applying reduceRight() to the same array results in the keys being order from last to first appearance (e.g. {'syntax.html': 2, 'asynchronous.html': 1,'index.html': 3,'for-loops.html': 1}), with the results being identical for both methods if order is ignored.

Finally, the last statement in listing 6-7 illustrates how the Array some() method checks if at least one element in the primeNumbers array is an even value, leveraging the evenNumber() callback function. You can see primeNumbers.some(evenNumber) returns true, since 2 is an even number.

The Map data type & Set data type baked-in for loop forEach() method with callback  

Following in the steps of the Array forEach() method, the Map data type and Set data type also have a forEach() method, intended to simplify how a loop is executed over all the elements in a Map or Set data structure by means of a callback function.

The Map forEach() method and Set forEach() method -- including their callback function -- have the same behaviors and limitations as the Array forEach() method described in the previous section: It's not possible to prematurely end a loop as a whole, once an invocation starts, all elements must be put through the callback function; it's possible to prematurely end a single iteration with a plain return statement in a callback function to terminate its workflow; and it's also possible to access the iteration value, index/counter, as well as the full data structure inside the callback function.

Listing 6-8 contains examples of the Map forEach() and Set forEach() methods, including small syntax variations that use plain functions and arrow functions, as well as a return statement to prematurely exit iterations.

Listing 6-8. Map forEach() method & Set forEach() method
let jsFramework =  new Map([["name","React"],["creator","Facebook"],["purpose","UIs"]]);
let vowels = ["a","e","i","o","u","e","i","o","a","u","u"];
let vowelsSet = new Set(vowels);

// Map with forEach()
jsFramework.forEach(function (value, key, map) {
  console.log(value);
  console.log("key:", key);
  console.log("map:", map);
});

// Set with forEach and ES6 arrow function
vowelsSet.forEach((value, index, set) => {
  console.log(value);
  console.log("index:", index);
  console.log("set:", set);
});

// Map forEach() with return
jsFramework.forEach(function (value) {
  // Move to next iteration when value "React"
  if (value == "React") {
    return;
  }
  console.log(value);
});

// Equivalent with ES6 arrow function
vowelsSet.forEach(value => {
   // Move to next iteration when value "e"
  if (value == "e") {
    return;
  }
  console.log(value);
});

Listing 6-8 starts by declaring the jsFramework map, followed by the vowels array that's used as the source to define a unique set of values in the vowelsSet set. Next, you can see the first forEach() example loops over all the elements in the jsFramework map, be aware the argument order for the callback function is value, key, map, which can be confusing for key-value data types like Map where you might expect the key to be first followed by the value, neverthless, this argument order adheres to the same conventions used in other data type forEach() methods. The second forEach() example loops over all the elements in the vowelsSet set, in this case, you can see the callback function uses arrow function syntax and outputs its three arguments: the value for a given iteration in the set, the index value for a given iteration and the set with all the values of the data structure.

The final two forEach() examples loop over the jsFramework map and vowelsSet set, with both making use of a return statement to prematurely exit certain iterations. Each of these examples uses different callback syntax, with the jsFramework map using an inline callback function and the vowelsSet an arrow callback function. In addition, notice the callbacks for these last two forEach() examples only use a single argument to access an iteration's value, confirming what was mentioned in the earlier Array forEach() method, callback functions used in forEach() methods can optionally declare up to three arguments to access an iteration's element value , its index/counter, as well as the full data structure.

If you're wondering why the Map and Set data types are limited to the forEach() method to perform loops, while the Array data type supports the forEach() method, as well as the every(), filter(), map(), reduce(), reduceRight() & some() methods to simulate more specialized loop logic, the reason is due to ECMAScript versioning and feature support. All these Array methods that use callback functions were introduced in ECMASCript 5 (ES5), while the Map and Set data types were introduced in ECMAScript 6 (ES2015). However, as part of ECMAScript 6 (ES2015) an enhanced for loop mechanism was added in the form of iterables and iterators, which is discussed in the upcoming sections. So the lack of methods to simulate for loops with callback functions in Map and Set data types, is because they rely more on the concept of iterables and iterators to perform loops.

The for of statement: Iterable and iterator protocols  

The iterable and iterator protocols[3] are among the most important additions to ECMAScript 6 (ES2015) and they're directly tied to for loops and the for of statement. The term protocol stems from the fact iterable and iterator are conventions followed by certain JavaScript data types. Like all protocols, they can be either followed or ignored, so a JavaScript data type may or may not support the iterable and iterator protocols. In ECMAScript 6 (ES2015), almost all JavaScript data types are -- or can become -- iterables.

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

An iterable represents a more powerful approach to loop 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 or continue statement).

In order to work with iterables and iterators you typically use the for of statement, which is specifically designed to loop over iterables. The general syntax to create a loop over an iterable with the for of statement is the following:

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

As you can see, for loops with the for of statement have a pretty straightforward syntax. But before you completely forget about the earlier for loop syntax techniques and drop them in favor of for of statements, it's very important to understand that for of statements only work on iterable objects.

Listing 6-9 illustrates how to create for loops with the for of statement.

Listing 6-9. For loops with for of statements
let language = "JavaScript";
let primeNumbers = [2,3,5,7,11];
let jsFramework =  new Map([["name","React"],["creator","Facebook"],["purpose","UIs"]])

// Loop over String
for (let value of language) {
    console.log(value);
}

// Loop over Array
for (let value of primeNumbers) {
    console.log(value);
}

// Loop over Map
for (let [key, value] of jsFramework){
     console.log(`langauge 
  key: ${key};
  value: ${value}`);
}

// break statement is valid in for of statement
for (let value of language) {
     if (value == "S") { 
       break;
     }
    console.log(value);
}

// continue statement is valid in for of statement
for (let value of primeNumbers) {
     if (value > 4 && value < 10) {
     continue;
    }
    console.log(value);
}

The first three examples in listing 6-9 perform a loop over a String, an Array and a Map using the for of statement to output each of its elements. Notice the third example uses a Map data type and the syntax for (let [key, value] of jsFramework) with two references -- one for key and another for values -- vs. a single reference like the first two examples, this is due to the way Map data types use key-value elements. It's worth pointing out a Map data type is used instead of an Object object data type, because an Object object data type isn't an iterable, so it can't be used directly in a for of statement -- but more details on this in the next section. The last two examples in listing 6-9 illustrate how it's possible to use break and continue statements to exit loops or iterations prematurely in a loop with a for of statement.

Iterable objects and the next() method: Behind the scenes of the for of statement & manually advancing over iterables  

The for of statement has some "behind the scenes" behaviors that allow it to progress over elements in a data structure:

The first behavior is the reason why the String, Array and Map data structures in listing 6-9 work with for of statements, all these data types are convertible to iterable objects since they have an @@iterator method, where @@ denotes a Symbol data type (e.g. [Symbol.iterator]()). It's also the reason why an Object object data type doesn't work with a for of statement, since it isn't directly convertible to an iterable object because it doesn't have an @@iterator method -- although the Object object data type does have certain methods to convert its elements to iterable objects, the details of which are provided shortly.

The second behavior implicitly calls an iterable object's next() method and it's this method that holds a powerful feature to manually advance over for loops. What if instead of letting the for of statement implicitly call the next() method on an iterable object, you could call it explicitly to have more control over the loop behavior ? That's entirely possible and is also the foundation of generators and yield expressions. But before we change topic, let's take a closer look at how to explicitly create iterable objects and use an iterator's next() method.

Listing 6-10 illustrates how to loop over iterable objects and use the next() method to manually advance over a data structure as if it were a for loop.

Listing 6-10. Iterable objects and the next() method
let jsFramework =  new Map([["name","React"],["creator","Facebook"],["purpose","UIs"]]);

// jsFramework data type
console.log("jsFramework is: %s", Object.prototype.toString.call(jsFramework))
// Create iterable object from jsFramework
let jsFrameworkIterator = jsFramework[Symbol.iterator]();
// jsFrameworkIterator data type
console.log("jsFrameworkIterator is: %s", Object.prototype.toString.call(jsFrameworkIterator))
// Create iterable object from jsFramework with entries(), 
// identical results to [Symbol.iterator]
let jsFrameworkIteratorEntries = jsFramework.entries();
// jsFrameworkIteratorEntries data type
console.log("jsFrameworkIteratorEntries is: %s", Object.prototype.toString.call(jsFrameworkIteratorEntries))
// Create iterable object from jsFramework with keys()
let jsFrameworkIteratorKeys = jsFramework.keys();
// jsFrameworkIteratorKeys data type
console.log("jsFrameworkIteratorKeys is: %s", Object.prototype.toString.call(jsFrameworkIteratorKeys))
// Create iterable object from jsFramework with values()
let jsFrameworkIteratorValues = jsFramework.values();
// jsFrameworkIteratorValues data type
console.log("jsFrameworkIteratorValues is: %s", Object.prototype.toString.call(jsFrameworkIteratorValues))

// Manually move over iterable jsFrameworkIterator with next()
console.log(jsFrameworkIterator.next().value); // [ 'name', 'React' ]
console.log(jsFrameworkIterator.next().value); // [ 'creator', 'Facebook' ]
// Output full object of next()
console.log(jsFrameworkIterator.next()); // { value: [ 'purpose', 'UIs' ], done: false }
// Output return value of next() confirming iterable reached end
console.log(jsFrameworkIterator.next()); //  value: undefined, done: true }

// Manually move over iterable jsFrameworkIteratorEntries with next()
console.log(jsFrameworkIteratorEntries.next().value); // [ 'name', 'React' ]
console.log(jsFrameworkIteratorEntries.next().value); // [ 'creator', 'Facebook' ]
// Output full object of next()
console.log(jsFrameworkIteratorEntries.next()); // { value: [ 'purpose', 'UIs' ], done: false }
// Output return value of next() confirming iterable reached end
console.log(jsFrameworkIteratorEntries.next()); //  value: undefined, done: true }

// Manually move over iterable jsFrameworkIteratorKeys with next()
console.log(jsFrameworkIteratorKeys.next().value); // name
console.log(jsFrameworkIteratorKeys.next().value); // creator
// Output full object of next()
console.log(jsFrameworkIteratorKeys.next()); // { value: 'purpose', done: false }
// Output return value of next() confirming iterable reached end
console.log(jsFrameworkIteratorKeys.next()); // { value: undefined, done: true }


// Manually move over iterable jsFrameworkIteratorValues with next()
console.log(jsFrameworkIteratorValues.next().value); // React
console.log(jsFrameworkIteratorValues.next().value); // Facebook
// Output full object of next()
console.log(jsFrameworkIteratorValues.next()); // { value: 'UIs', done: false }
// Output return value of next() confirming iterable reached end
console.log(jsFrameworkIteratorValues.next()); // { value: undefined, done: true }

Listing 6-10 creates a map and assigns it to the jsFramework reference with a log statement that confirms it's an [object Map] data type. This means the jsFramework is not yet an iterable object, it can become an iterable object if placed in a for of statement, but then we wouldn't be able to use the next() method. The solution is to explicitly create an iterable object from the jsFramework map, for which there are various alternatives shown in listing 6-10:

A key takeaway of all these alternatives to create iterable objects is they all output a [object Map Iterator] data type, indicating they're iterables and can thus operate with the next() method. Equipped with various iterable objects, notice how the next() method is called on each iterable object and how on each call the iterable object moves forward to its next element.

The next() method returns an Object 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 each iterable object, 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 statements determine when a loop is done.

Iterable objects produced by data type methods: Things you can put through for of statements

This last section showcased how the Map data type has several methods to produce iterable objects, which in turn makes them candidates to either use the iterable next() method on them, or more practically, candidates to run through a for of statement.

Therefore, there are many alternatives to produce iterable object data structures that can be run through for of statements, beyond the basic examples presented in listing 6-9 which simply use standard data types that support the @@iterator method.

Tip The Object object data type due to its ample and legacy baggage can't be converted to an iterable object. However, throughout the years this data type has added methods that allow it to be converted to an Array data type (e.g. Object.entries(), Object.keys(), Object.values()). This in turn allows an Object data type to be converted to an Array first which can then be converted to an iterable object with Array methods that produce iterable objects.
Another alternative is to use a Map data type which does support being converted to an iterable object vs. an Object data type.

Array data type methods that produce iterable objects: entries(), keys(), values() & [Symbol.iterator]()  

Iteration methods available in the Array data type are the following:

Generators and yield expressions  

A problem with 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 the for of statement, iterable and iterator protocols, 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 listing 6-11

Listing 6-11. Generators with the * syntax
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 6-12 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 6-12. 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 6-12 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.

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

  2. https://en.wikipedia.org/wiki/Fold_(higher-order_function)    

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