Understanding Higher Order Loops in Javascript
How the modern for...of loop simplifies looping through arrays, strings, maps, and more in JavaScript.
Table of contents
If you're tired of writing repetitive code to iterate over arrays and objects, it’s time to discover the higher-order loops.
Higher-order loops provide an easy and powerful way to work with data collections, making your code clearer, shorter, and easier to maintain.
Unlike traditional loops, higher-order loops are methods that take a callback function and apply it to each element in a collection.
Yes, this is a defining feature of higher-order loops: they accept a function as an argument to process each element in a collection.
So Instead of using traditional for
or while
loops, we will use high-order loops use functions like map
, filter
, reduce
, and others to handle looping in a more abstract and often more readable way.
Additionally, for...of
and for...in
loops provide a more straightforward syntax for iterating over values and properties without the need for a callback function.
For Of Loop
The for...of
loop is a modern iteration statement in JavaScript that simplifies looping over iterable objects such as arrays, strings, maps, sets, and other objects that implement the iterable protocol. It allows you to traverse all the elements of an iterable without having to worry about the index or the length
property.
Syntax
for (variable of iterable) {
// statement
}
variable
: A variable that will be assigned the value of each iterated element.iterable
: An object that has iterable properties, like an array, string, set, map, etc.
The for...of
loop is particularly useful when you need to iterate over all elements of an array, string, or any other iterable, without the need to access the index or perform specific operations based on the index.
Iterating Over an Array:
const numbers = [1, 2, 3, 4, 5]; for (const number of numbers) { console.log(number); } // Output: 1 2 3 4 5
This code snippet creates an array called
numbers
containing the values 1, 2, 3, 4, and 5. It then uses afor...of
loop to iterate over each element in thenumbers
array. During each iteration, the current element is assigned to the variablenumber
, and then theconsole.log()
function is used to output the value ofnumber
to the console. This loop will output each number in thenumbers
array on a separate line.Iterating Over a String:
for (const char of message) {
console.log(char);
}
// Output: H e l l o
Iterating Over a Map:
Maps are collections of key-value pairs where each key is unique. When iterating over a map, you use the for...of
loop along with destructuring to access each key-value pair.
const userRoles = new Map();
userRoles.set('John', 'Admin');
userRoles.set('Jane', 'Editor');
userRoles.set('Doe', 'Viewer');
for (const [user, role] of userRoles) {
console.log(`${user}: ${role}`);
}
// Output: John: Admin
// Jane: Editor
// Doe: Viewer
Map Characteristics:
Unique Keys: Each key in a map must be unique. Adding a key that already exists will overwrite the previous value.
Maintained Order: Maps maintain the insertion order of the key-value pairs. The first element inserted will be the first one iterated over, and so on.
Iterating Over a Set:
A Set
in JavaScript is a collection of unique values. It can hold any type of values, whether primitive or object references.
Characteristics of a Set:
Unique Values: Each value must be unique; duplicates are not allowed.
Order of Insertion: Maintains the order of element insertion.
Iterable: Sets are iterable, allowing you to use loops such as
for...of
.
const uniqueNumbers = new Set([1, 2, 3, 4, 5]);
for (const number of uniqueNumbers) {
console.log(number);
}
// Output: 1 2 3 4 5
Using
for...of
with thearguments
Object:
The arguments
object is an array-like object accessible inside functions that contain the values of the arguments passed to that function.
Note that it is not a real array, so array methods like forEach
or map
cannot be directly used on it without converting it to a real array first. Each argument can be accessed using a zero-based index.
function sum() {
let total = 0;
for (const num of arguments) {
total += num;
}
return total;
}
console.log(sum(1, 2, 3, 4)); // Output: 10
This code snippet defines a function called sum
that calculates the total sum of all the arguments passed to it. It uses a for...of
loop to iterate over the arguments
object, which contains all the arguments passed to the sum
function.
Since the sum function definition does not specify a fixed number of parameters,it can accept a variable number of arguments and it uses the arguments object to access all the arguments passed to the function.
The for...of loop then iterates over these arguments, allowing the function to handle any number of arguments provided when it is called.
Using
for...of
to iterate on Object:
The for...of
loop is not directly applicable to plain objects because they are not iterable by default. However, you can still iterate over the properties of an object using alternative methods, such as Object.keys()
, Object.values()
, or Object.entries()
, combined with the for...of
loop.
Iterating Over Object Properties Using for...of
Using
Object.keys()
: Iterates over the keys of the object.Using
Object.values()
: Iterates over the values of the object.Using
Object.entries()
: Iterates over the key-value pairs of the object.
Examples
- Iterating Over Keys
const user = {
name: 'John',
age: 30,
profession: 'Developer'
};
for (const key of Object.keys(user)) {
console.log(key);
}
// Output:
// name
// age
// profession
- Iterating Over Values
const user = {
name: 'John',
age: 30,
profession: 'Developer'
};
for (const value of Object.values(user)) {
console.log(value);
}
// Output:
// John
// 30
// Developer
- Iterating Over Entries
const user = {
name: 'John',
age: 30,
profession: 'Developer'
};
for (const [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`);
}
// Output:
// name: John
// age: 30
// profession: Developer
For IN Loop
The for...in
loop in JavaScript is used to iterate over the enumerable properties of an object. This includes properties that are inherited through the prototype chain unless those properties are non-enumerable.
To understand the difference between For of and For in loop read the article Understanding JavaScript Loops: for...in vs for...
for (variable in object) {
// statement
}
variable
: A variable that will be assigned the key (property name) of each iterated property.object
: The object whose enumerable properties are iterated over.
The for...in
loop is particularly useful for iterating over the properties of an object, allowing you to access both the property names (keys) and their values.
Best Practices
Object Check: Ensure you are iterating over objects and not arrays, as for...in is not meant for array iteration (use for...of or array methods like forEach for arrays).
Own Properties Check: Use hasOwnProperty to filter out properties inherited through the prototype chain if needed.
The for...in
loop is particularly useful for iterating over the properties of an object, allowing you to access both the property names (keys) and their values.
Best Practices
Object Check: Ensure you are iterating over objects and not arrays, as for...in is not meant for array iteration (use for...of or array methods like forEach for arrays).
Own Properties Check: Use hasOwnProperty to filter out properties inherited through the prototype chain if needed.
Avoid with Arrays: Avoid using for...in with arrays due to potential issues with non-integer keys and inherited properties.
Examples
Let's create a plain object representing a car and use the for...in loop to iterate over its properties:
const car = {
make: 'Toyota',
model: 'Corolla',
year: 2020,
color: 'Red'
};
for (const key in car) {
console.log(`${key}: ${car[key]}`);
}
//Output
make: Toyota
model: Corolla
year: 2020
color: Red
Iterating Over Object Properties:
Iterating over object properties refers to examining each key-value pair in a JavaScript object. This is commonly done using the for...in
loop, which iterates over all enumerable properties of an object, including those inherited through the prototype chain. To restrict iteration to an object's own properties, the hasOwnProperty
method is used.
const user = {
name: 'John',
age: 30,
profession: 'Developer'
};
for (const key in user) {
if (user.hasOwnProperty(key)) { // Ensuring it's an own property
console.log(`${key}: ${user[key]}`);
}
}
// Output:
name: John
age: 30
profession: Developer
Iterating Over Inherited Properties:
When you iterate over inherited properties, you are accessing properties that are not defined directly on the object but are inherited from its prototype. This can be done using the for...in
loop. To demonstrate:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.profession = 'Developer';
const john = new Person('John', 30);
for (const key in john) {
console.log(`${key}: ${john[key]}`);
}
// Output:
name: John
age: 30
profession: Developer
Using for...in with Arrays (Not Recommended):
Arrays in JavaScript are best iterated using other methods, such as for...of
, forEach()
, or traditional for
loops, as these provide more predictable outcomes and do not include prototype properties in the iteration. For example:
const numbers = [1, 2, 3, 4, 5];
for (const index in numbers) {
console.log(index);
}
// Output:
// 0
// 1
// 2
// 3
// 4
If you want to print both the indices and the values, you can combine the for...in
loop with the array access:
const numbers = [1, 2, 3, 4, 5];
for (const index in numbers) {
console.log(`Index: ${index}, Value: ${numbers[index]}`);
}
// Output:
// Index: 0, Value: 1
// Index: 1, Value: 2
// Index: 2, Value: 3
// Index: 3, Value: 4
// Index: 4, Value: 5
Iterating Over a Map
The for...in
loop is not suitable for iterating over Maps in JavaScript. Instead, you should use the for...of
loop to iterate over Maps. However, if you want to iterate over the keys, values, or entries of a Map, you can use the Map's built-in methods: keys()
, values()
, and entries()
, combined with the for...of
loop.
- Iterating Over Keys:
const userRoles = new Map();
userRoles.set('John', 'Admin');
userRoles.set('Jane', 'Editor');
userRoles.set('Doe', 'Viewer');
for (const key of userRoles.keys()) {
console.log(key);
}
// Output:
// John
// Jane
// Doe
- Iterating Over Values:
const userRoles = new Map();
userRoles.set('John', 'Admin');
userRoles.set('Jane', 'Editor');
userRoles.set('Doe', 'Viewer');
for (const value of userRoles.values()) {
console.log(value);
}
// Output:
// Admin
// Editor
- Iterating Over Entries:
const userRoles = new Map();
userRoles.set('John', 'Admin');
userRoles.set('Jane', 'Editor');
userRoles.set('Doe', 'Viewer');
for (const [key, value] of userRoles.entries()) {
console.log(`${key}: ${value}`);
}
// Output:
// John: Admin
// Jane: Editor
// Doe: Viewer
- Iterating Directly Over the Map (Entries by Default):
You can iterate directly over a Map
in JavaScript using a for...of
loop. By default, this loop provides each entry as a [key, value]
pair:
const userRoles = new Map();
userRoles.set('John', 'Admin');
userRoles.set('Jane', 'Editor');
userRoles.set('Doe', 'Viewer');
for (const [key, value] of userRoles) {
console.log(`${key}: ${value}`);
}
// Output:
// John: Admin
// Jane: Editor
// Doe: Viewer
forEach Loop
The forEach loop is a method available on arrays and other iterable collections (like Maps and Sets) in JavaScript. It allows you to execute a provided function once for each array element.
The forEach
method requires a callback function that will be executed once for each element in the array. This callback function can take up to three arguments:
array.forEach(function(element, index, array) {
// statement
});
element
: The current element being processed in the array.index
(optional): The index of the current element being processed in the array.array
(optional): The array thatforEach
was called upon.
The forEach method is particularly useful when you need to perform a specific action for each element in an array, such as logging values, updating elements, or performing calculations.
Best Practices
Side Effects: Use
forEach
for performing actions that produce side effects, like modifying elements, logging, or updating the DOM.Avoid Returns:
forEach
is not designed for returning values or constructing new arrays. Usemap
,filter
, orreduce
for these purposes.Arrow Functions: Use arrow functions for concise syntax, especially for simple operations.
Examples
Basic Iteration Over an Array:
const numbers = [1, 2, 3, 4, 5];
/* you can write this also
numbers.forEach(function (item) {})
*/
numbers.forEach((number) => {
console.log(number);
});
// Output:
// 1
// 2
// 3
// 4
// 5
Using Index and Array Parameters:
const numbers = [1, 2, 3, 4, 5];
numbers.forEach((number, index) => {
console.log(`Index: ${index}, Value: ${number}`);
});
// Output:
// Index: 0, Value: 1
// Index: 1, Value: 2
// Index: 2, Value: 3
// Index: 3, Value: 4
// Index: 4, Value: 5
Modifying Elements:
let numbers = [1, 2, 3, 4, 5];
numbers.forEach((number, index, arr) => {
arr[index] = number * 2;
});
console.log(numbers);
// Output: [2, 4, 6, 8, 10]
Using
forEach
with a Map:
const userRoles = new Map();
userRoles.set('John', 'Admin');
userRoles.set('Jane', 'Editor');
userRoles.set('Doe', 'Viewer');
userRoles.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// Output:
// John: Admin
// Jane: Editor
// Doe: Viewer
Using
forEach
with a Set:
const uniqueNumbers = new Set([1, 2, 3, 4, 5]);
uniqueNumbers.forEach((value) => {
console.log(value);
});
// Output:
// 1
// 2
// 3
// 4
// 5
Passing function in for each loop
Passing functions to the forEach method is a common practice in JavaScript. You can pass both named and anonymous functions as the callback function for forEach. Here's how you can do it with various examples:
- Using Anonymous Functions
You can pass an anonymous function directly to the forEach method.
const numbers = [1, 2, 3, 4, 5];
numbers.forEach(function(number) {
console.log(number);
});
// Output:
// 1
// 2
// 3
// 4
// 5
- Using Arrow Functions
Arrow functions provide a concise syntax for writing functions.
const numbers = [1, 2, 3, 4, 5];
numbers.forEach(number => {
console.log(number);
});
// Output:
// 1
// 2
// 3
// 4
// 5
Using Named Functions
You can define a named function and pass it to the forEach
method.
const numbers = [1, 2, 3, 4, 5];
function logNumber(number) {
console.log(number);
}
numbers.forEach(logNumber);
// Output:
// 1
// 2
// 3
// 4
// 5
Using Named Functions with Additional Parameters
If your named function needs additional parameters, you can create a higher-order function that returns a function with the required parameters.
const numbers = [1, 2, 3, 4, 5];
function createLogger(prefix) {
return function(number) {
console.log(`${prefix}: ${number}`);
};
}
const logWithPrefix = createLogger('Number');
numbers.forEach(logWithPrefix);
// Output:
// Number: 1
// Number: 2
// Number: 3
// Number: 4
// Number: 5
Using
forEach
with Objects Like Maps and Sets
You can also pass functions to the forEach
method when working with Maps and Sets.
- With Maps
const userRoles = new Map();
userRoles.set('John', 'Admin');
userRoles.set('Jane', 'Editor');
userRoles.set('Doe', 'Viewer');
function logRole(value, key) {
console.log(`${key}: ${value}`);
}
userRoles.forEach(logRole);
// Output:
// John: Admin
// Jane: Editor
// Doe: Viewer
- With Sets
const uniqueNumbers = new Set([1, 2, 3, 4, 5]);
function logValue(value) {
console.log(value);
}
uniqueNumbers.forEach(logValue);
// Output:
// 1
// 2
// 3
// 4
// 5
Conclusion
Passing functions to the forEach
loop provides flexibility and reusability in your code. You can use anonymous functions, arrow functions, named functions, and even higher-order functions to achieve different behaviors during iteration. This makes the forEach
method a powerful tool for performing operations on arrays and other iterable collections.
Differences Between forEach and Other Loops
NoEarly Exit: Unlike
for
,for...of
, orfor...in
loops, theforEach
method cannot be broken out of usingbreak
orreturn
. It will iterate over all elements unless an exception is thrown.NoReturn Value: The
forEach
method always returnsundefined
. It is designed to perform side effects rather than to transform data.Simplicity:
forEach
provides a simple and readable way to iterate over arrays and collections, especially when you don’t need to construct new arrays or handle return values.
Array of objects
When you have an array of objects, you can use the forEach
method (or other iteration methods) to iterate over each object in the array and perform operations on its properties. Here are various ways to iterate over an array of objects:
Using
forEach
Method
Example 1: Logging Each Object
const users = [
{ name: 'John', age: 30, profession: 'Developer' },
{ name: 'Jane', age: 25, profession: 'Designer' },
{ name: 'Doe', age: 22, profession: 'Tester' }
];
users.forEach(user => {
console.log(user);
});
// Output:
// { name: 'John', age: 30, profession: 'Developer' }
// { name: 'Jane', age: 25, profession: 'Designer' }
// { name: 'Doe', age: 22, profession: 'Tester' }
Example 2: Logging Specific Properties
users.forEach(user => {
console.log(`Name: ${user.name}, Age: ${user.age}`);
});
// Output:
// Name: John, Age: 30
// Name: Jane, Age: 25
// Name: Doe, Age: 22
Using
map
Method
The map method creates a new array with the results of calling a provided function on every element in the calling array.
const names = users.map(user => user.name);
console.log(names);
// Output: ['John', 'Jane', 'Doe']
Using for...of Loop
The for...of loop is another way to iterate over an array of objects.
for (const user of users) {
console.log(Name: ${user.name}, Age: ${user.age});
}
// Output:
// Name: John, Age: 30
// Name: Jane, Age: 25
// Name: Doe, Age: 22
Using
for
Loop
You can also use the traditional for loop.
for (let i = 0; i < users.length; i++) {
console.log(`Name: ${users[i].name}, Age: ${users[i].age}`);
}
// Output:
// Name: John, Age: 30
// Name: Jane, Age: 25
// Name: Doe, Age: 22
Using
reduce
Method
The reduce
method can be used to accumulate values based on the objects in the array.
Example: Summing Ages
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
console.log(totalAge);
// Output: 77
Combining Iteration with Conditionals
You can combine iteration with conditional statements to filter or process specific objects.
Example: Filtering and Logging Specific Users
users.forEach(user => {
if (user.age > 24) {
console.log(user);
}
});
// Output:
// { name: 'John', age: 30, profession: 'Developer' }
// { name: 'Jane', age: 25, profession: 'Designer' }
Conclusion
Iterating over an array of objects can be done using various methods in JavaScript, each with its own advantages:
forEach
: Simple iteration for performing actions on each element.map
: Creates a new array by applying a function to each element.for...of
: Clean and readable syntax for iterating over iterable objects.for
: Traditional loop that provides full control over the iteration process.reduce
: Accumulates values based on the elements in the array.