The keyword for
does a lot of heavy lifting in Javascript. The classic for
loop, the for...of
statement, the for...in
statement, and the forEach
method all have commonalities, but aren’t interchangeable in all (or even most) circumstances. Here are the explanations of each feature.
Classic for
loop
A familiar pattern in many languages, the for
loop executes code inside of its brackets until a termination condition is met. The condition usually hinges on a counter variable, traditionally called i
for index, which is initialized once and then incremented or decremented by some amount with every iteration of the loop. Because i
changes value with every loop iteration, the code inside the loop can be called on different elements of a collection when i
is used as an index value for that collection.
const arr = [1, 2, 3, 4];
for (let i = 0; i < arr.length; ++i) {
console.log(arr[i]);
// outputs 1, 2, 3, 4
}
The ability to specify the amount by which the counter increases or decreases provides immense flexibility compared to the other language features on this list. For example, starting the counter at 0
and incrementing by 2
allows you to read or modify only the elements of a collection that are indexed by even integer. If there is a use case that calls for this, the for
loop will finish in half the time of the foreach
method or the for...
of statement.
const arr = [1, 2, 3, 4];
for (let i = 0; i < arr.length; i = i + 2) {
console.log(arr[i]);
// outputs 1, 3
}
The for
loop can also be configured to terminate prior to the satisfaction of its termination condition, using the break
keyword. This can be leveraged as an additional optimization if there is no need for the loop to continue running after a certain element is found.
const arr = [1, 2, 3, 4];
for (let i = 0; i < arr.length; ++i) {
console.log(arr[i]);
if (arr[i] == 2) {
break;
}
// outputs 1, 2
}
The granular level of control allowed by the for
loop makes it one of the most versatile, if verbose, options for iteration in the Javascript landscape.
for...
of statement
The for...of
statement doesn’t expose a counter variable like a regular for
loop; when called on a compatible collection, it will iterate once for every item in that collection, or until the break
keyword is encountered. One advantage of this design is that for...of
statements can iterate over collections that aren’t keyed by integers. The non-exhaustive list of compatible collections includes strings, arrays, sets, maps and node lists (DOM collections). Technically, any built-in data structure that implements an @@iterator
method can be traversed with a for...of
loop. Note that, somewhat counterintuitively, this does not include the standard Javascript object.
const arr = [1, 2, 3, 4];
const s = new Set([1, 2, 3, 4]);
// iterate over array
for (let element of arr) {
console.log(element)
// outputs 1, 2, 3, 4
}
// iterate over set
for (let element of s) {
console.log(element);
// outputs 1, 2, 3, 4
}
When looping over a map, every iteration returns a key/value pair. This singular value can be split and assigned to respective variables using destructuring syntax.
const m = new Map([['a', 1], ['b', 2], ['c', 3]]);
// iterate over map
for (let [key, value] of m) {
console.log(key);
// outputs a, b, c
console.log(value);
// outputs 1, 2, 3
}
Raw Javascript objects are not iterable because they do not implement the @@iterator
method. However, the object constructor, referenced as Object()
, contains several useful static methods that return an object’s key/value pairs as an array, thus making them compatible with a for...of
statement.
let obj = {a: 1, b: 2, c: 3};
//iterate over object keys
for (let key of Object.keys(obj)) {
console.log(key)
// outputs a, b, c
}
//iterate over object values
for (let value of Object.values(obj)) {
console.log(value)
// outputs 1, 2, 3
}
//iterate over object key/value pairs
for (let [key, value] of Object.entries(obj)) {
console.log(key);
// outputs a, b, c
console.log(value)
// outputs 1, 2, 3
}
The arrays returned by the keys
, values
, and entries
static methods will not include key/value pairs that an object inherits from the its prototypal inheritance chain. To iterate over an object and access these properties, the for...in
statement must be used.
for...in
statement
The for...in
statement has a relatively niche use case compared to the other entries on this list. While the for...of
loop is able to iterate over Object properties with the assistance of the Object constructor’s static methods, the for...in
loop does this natively.
let obj = {a: 1, b: 2, c: 3};
//iterate over object keys
for (let key in obj) {
console.log(key);
// outputs a, b, c
console.log(obj[key]);
// outputs 1, 2, 3
}
For most situations however, for...of
is still the preferable (if slightly longer) syntax because of its ability to return keys, values, or both as a pair, whereas a for...in
statement will only return keys.
The one feature that is unique to for...in
is the ability to iterate over inherited object properties.
let obj = {a: 1, b: 2, c: 3};
// create child object with additional property
let childObj = Object.create(obj);
childObj.d = 4;
//iterate over object keys with for...of
for (let key of Object.keys(childObj)) {
console.log(key)
// outputs d
// does NOT output properties inherited from obj
}
//iterate over object keys with for...in
for (let key in childObj) {
console.log(key)
// outputs d, a, b, c
// DOES output properties inherited from obj
}
In theory, a for...in
statement could be configured to ignore inherited properties by utilizing an if
clause inside of the loop, although such an implementation would negate its primary feature.
let obj = {a: 1, b: 2, c: 3};
// create child object with additional property
let childObj = Object.create(obj);
childObj.d = 4;
//iterate over object keys with for...in
for (let key in childObj) {
if (childObj.hasOwnProperty(key)) {
console.log(key);
}
// outputs d
// DOES NOT output properties inherited from obj
}
forEach
method
Many of Javascript’s standard data structures, including arrays, sets, maps and node lists, come bundled with their own forEach
method. The method signature is the same for all of them; a callback function (usually an anonymous arrow function) that is successively executed on each element of the collection. The element is passed into the callback function as an argument.
let arr = [1, 2, 3, 4]
arr.forEach(element => console.log(element));
\\ outputs 1, 2, 3, 4
The callback function also accepts optional parameters of a counter variable, and a reference to the array upon which the method is acting.
let arr = [1, 2, 3, 4]
arr.forEach((element, index, arr) => {
console.log(element);
// outputs 1, 2, 3, 4
console.log(index);
// outputs 0, 1, 2, 3
console.log(arr[index]);
// outputs 1, 2, 3, 4
});
The availability of a counter variable is an advantage that forEach
has over the for...of
statement. Like the for...of
statement, forEach
can be used in conjunction with static object methods to iterate over object properties.
let obj = {a: 1, b: 2, c: 3};
Object.entries(obj).forEach(([key, value], index) => {
console.log(key);
// outputs a, b, c
console.log(value);
// outputs 1, 2, 3
console.log(index);
// outputs 0, 1, 2
});
The conciseness of the syntax, the option of a counter variable, and its availability in multiple data structures make forEach
a powerful mechanism for iterating.
In summary…
for
loops provide a counter variable to use as an index when accessing elements in collections. The ability to manipulate the loop’s control flow by incrementing or decrementing the counter variable is useful, but only if the collection is keyed by integers.- the
for...of
statement can iterate over almost any collection (including those keyed by strings), although objects require the intermediary step of calling an object constructor static method for their properties to be iterable. - the
for...
in statement cycles through an object’s keys without any helper methods, and will access inherited keys by default. - the
forEach
method is common to many data structures and provides a concise syntax for specifying a function to call on every member of a collection. The callback function has access to the current element, a counter variable, and a reference to the array being acted on, which allows for considerable tweaking of the control flow.