Introduction
A higher order function (HOF) is a function with the ability to take other functions as arguments or return them as results. To fully grasp this concept, we must first understand JavaScript functions.
In JavaScript, functions are considered first-class citizens, meaning they can be stored in variables, passed as arguments to other functions, and even returned from functions. This flexibility allows for more dynamic and reusable code.
A higher order function is simply a function that accepts another function as a parameter or returns one. There are many higher order functions in JavaScript, but in this article, we will focus on the most commonly used ones, those you are likely to encounter in everyday programming.
Our primary focus will be on three essential higher-order functions: map()
, filter()
, and reduce()
. These array methods play a crucial role in problem-solving, enhancing code readability, and reducing the number of lines of code. Let’s begin with the map()
method.
The Map Method
The map()
method in JavaScript is an array function that generates a new array by applying a specified operation to each element of an existing array. Unlike forEach()
, which iterates over an array without returning a new one, map()
preserves the original array while producing a transformed version. Let’s explore an example to see how it works.
const number = [1, 2, 3, 4, 5]
//Map Method
const result = number.map(num => {
return num * 2
})
console.log(result) // output [ 2, 4, 6, 8, 10 ]
console.log(numbers) // output [ 1, 2, 3, 4, 5 ]
// forEach Method
const result = number.forEach((num, index) => {
return num * 2
})
console.log(result) // undefined
console.log(number) // output [ 1, 2, 3, 4, 5 ]
From the example above, we see that the map()
method creates a new array without modifying the original one, while forEach()
executes a function for each element but does not return a new array.
Now, let’s explore a more detailed example using an array of objects.
const fruits = [
{id: 1, name: 'apple'},
{id: 2, name: 'banana'},
{id: 3, name: 'orange'},
{id: 4, name: 'mango'},
{id: 5, name: 'pear'}
]
const result = fruits.map((fruit, index, array) => {
return fruit.name.toUpperCase()
})
console.log(result)
In the example above, the map()
method accepts three parameters: the current element being processed, the index of that element, and the original array on which .map()
was called. However, only the element is required, while the index
and array
parameters are optional. In this case, we use fruit to represent each element, but you can choose any variable name that aligns with the array's context for better readability. Compared to a for loop, the map()
method is more concise and readable, especially when used with arrow functions.
The Filter Method
The next higher order function we'll explore is the filter method. In JavaScript, the filter()
method is used to generate a new array containing only the elements that satisfy a given condition. This means that the filter()
method must return a boolean value. It does not modify the original array, and only elements that evaluate to true are included in the new array.
Just like the map() method, the filter() method also accepts three parameters: the current element being processed, its index, and the original array. Let's take a look at the example below.
const people = [
{id: 1, name: 'scott', age: 23, occupation: 'developer'},
{id: 2, name: 'john', age: 34, occupation: 'designer'},
{id: 3, name: 'doe', age: 18, occupation: 'project manager'},
{id: 4, name: 'smith', age: 40, occupation: 'QA tester'},
{id: 5, name: 'water', age: 25, occupation: 'Technical writer'}
]
const result = people.filter((person) => {
return person.age > 20
})
console.log(result)
In the example above, we have an array of objects where each object represents a person with properties such as id
, name
, age
, and occupation
. To extract specific data from this array, we use the filter()
method.
Here, we filter out people whose age is greater than 20. The method iterates through the array, checking each person's age. When it encounters doe, whose age is 18, it does not meet the condition, so it is excluded from the new array. The method then returns only the objects where the age is greater than 20. The output in the console will be:
[
{ id: 1, name: 'scott', age: 23, occupation: 'developer' },
{ id: 2, name: 'john', age: 34, occupation: 'designer' },
{ id: 4, name: 'smith', age: 40, occupation: 'QA tester' },
{ id: 5, name: 'water', age: 25, occupation: 'Technical writer' }
]
Let's explore another example.
const people = [
{id: 1, name: 'scott', age: 23, occupation: 'developer'},
{id: 2, name: 'john', age: 34, occupation: 'designer'},
{id: 3, name: 'doe', age: 18, occupation: 'developer'},
{id: 4, name: 'smith', age: 40, occupation: 'QA tester'},
{id: 5, name: 'water', age: 25, occupation: 'Technical writer'}
]
const result = people.filter((person)=> {
return person.occupation === 'developer'
})
console.log(result)
In this example, we use the filter()
method to extract only the people whose occupation is "developer". The method iterates through each object in the people array, checking if the occupation property matches "developer". If the condition is met, the object is included in the new array. The filtered result is then stored in the result variable.
Since only Scott and Doe have "developer" as their occupation, the filtered array will contain only these two objects:
[
{ id: 1, name: 'scott', age: 23, occupation: 'developer' },
{ id: 3, name: 'doe', age: 18, occupation: 'developer' }
]
This approach is useful when working with large datasets, allowing developers to extract specific subsets of data efficiently without modifying the original array. The filter()
method is particularly powerful in scenarios where multiple conditions need to be applied, making it a key function in JavaScript for handling arrays dynamically.
The Reduce Method
Now, let's dive into one of the most powerful yet often confusing higher-order functions: the reduce()
method. This method iterates through an array and reduces its elements into a single value. It is widely used for operations like summing numbers, flattening arrays, and more.
The reduce()
method takes a callback function and an initial value as parameters. The callback function processes each element, accumulating a result that carries over to the next iteration. This process continues until all elements have been processed, ultimately returning a final value.
Sounds a bit complex? Let’s break it down with an example for better clarity.
const number = [1, 2, 3, 4, 5]
const result = number.reduce((acc, curr) => {
return acc + curr
}, 0)
console.log(result) //output 15
Here, we have an array of numbers, and we use the reduce()
method to sum them up. The reduce() method takes a callback function with two parameters: acc
(the accumulator) and curr
(the current element). Keep in mind that you can use any variable names of your choice.
Inside the callback function, we add acc and curr and return the result. Additionally, we provide an initial value of 0
, which serves as the starting value for acc
. The function executes as follows:
- The initial value
0
is added to the first number in the array 1
,
resulting in 1
.
1
is then added to 2
, yielding 3
.
3
is added to 3
, making it 6
, and so on.
- Ultimately, the total sum is
15
, which is logged to the console
If we omit the initial value, the first element of the array 1
becomes the accumulator, and reduce()
processes the rest of the array accordingly.
Chaining Higher-Order Functions
Chaining Higher Order Functions
Higher order functions provide a powerful technique called chaining, which allows us to write clean, efficient, and readable code. Since most higher-order functions return a new array (except reduce(), which returns a single value), we can pass the output of one function directly as the input to the next.
Let's take a look at an example:
const products = [
{ name: 'Laptop', price: 1200, category: 'Electronics' },
{ name: 'Phone', price: 800, category: 'Electronics' },
{ name: 'Tablet', price: 600, category: 'Electronics' },
{ name: 'Book', price: 20, category: 'Stationery' },
{ name: 'Pen', price: 5, category: 'Stationery' }
];
// Chaining filter, map, and reduce
const totalElectronicsCost = products.filter(product => product.category === 'Electronics')
.map(product => product.price)
.reduce((sum, price) => sum + price, 0)
console.log(totalElectronicsCost); // Output: 2600
In the example below, we have an array of objects representing different products, each with a name, price, and category property. We first filter the products array to get only items in the "Electronics" category. This returns three products: Laptop
, Phone
, and Tablet
. Next, we extract only the price values from the filtered array using the map() method, chaining it with the dot (.) operator. Finally, we sum up all the extracted prices using the reduce()
method, starting with an initial value of 0
. Chaining higher-order functions makes the code more readable, modular, and efficient. Instead of writing multiple loops, we achieve the same result in a clean and concise way.
Conclusion
Higher order functions like map()
, filter()
, and reduce()
are powerful tools in JavaScript that allow us to manipulate arrays efficiently and write clean, readable, and maintainable code. By using these functions, we can simplify complex operations, avoid unnecessary loops, and improve performance. Additionally, chaining these functions enables seamless data transformation in a structured and concise manner. Mastering higher order functions is essential for any JavaScript developer, as they provide a functional approach to handling arrays and enhance code reusability. Keep practicing and experimenting with different use cases to strengthen your understanding!