Table of Contents
Higher-order functions are one of the topics that can be hard to understand. This article will help you understand what higher-order functions are and how to work with them. You will also learn about the difference between high-order and first order functions and about high-order functions built-in in JavaScript.
JavaScript and first class functions
In JavaScript, functions are treated as first-class citizens. What this means is that in JavaScript, other functional programming languages, functions are actually objects. They are a special type of Function
objects. This means that all the things you can do with other types such as object, string, or number, you can do with functions as well.
Assigning functions to variables
Assigning functions to variables is one common thing you can do with them. This is also called creating function expressions. The reason for this name is that you create a function inside an expression. You then assign this expression to a variable. From now on you can use the variable name to reference, and call, this function.
// Create function with function expression
// and assign it to a variable
const myFunc = function() {
return 'Hello'
}
One thing you have to remember when you use function expression is that they are not hoisted. This means that you can’t use function expressions before you declare them. This is not an issue if you use function declaration, using the function
keyword. Functions defined with are hoisted and you can use them before you declare them.
// This doesn't work:
// Try to use function
// created with function expression
// before it is declared
myFunc()
// myFunc is not defined
const myFunc = function() {
return 'Hello'
}
// This works:
// Try to use function
// created with function declaration
// before it is declared
myFunc()
// 'Hello'
function myFunc() {
return 'Hello'
}
Passing functions as arguments
One thing you may have seen is passing functions as arguments to other functions. These passed functions are usually passed as the last argument and later used as callbacks. A callback is a function that gets executed when all operations that have been completed. This is a common practice.
This works due to the fact that JavaScript is single-threaded programming language. This means that only one operation can be executed at the time. So, when you pass a callback function, and invoke it at the end, it will be invoked when all preceding operations are completed.
Callbacks were very popular before promises and async/await. Unfortunately, they often led to something called a callback hell.
// Create a function that takes another function
// as a parameter and uses that function
// as a callback
function myFunction(text, callbackFunc) {
console.log(text)
callbackFunc()
}
// Create callback function
function myCallbackFunc() {
console.log('I am a callback function')
}
// Invoke "myFunction()" function
// passing some text and callback function
myFunction('This will show before invoking callback.', myCallbackFunc)
// 'This will show before invoking callback.'
// 'I am a callback function'
One situation when functions are passed as arguments frequently is when you work with event listeners. The addEventListener
method takes three parameters: type of event to listen to, object or function and options. The second parameter, the function, is the callback. When specific addEventListener
is triggered this callback function is invoked.
// Find button in the DOM
const button = document.querySelector('#btn')
// Create function to handle the event
function handleButtonClick() {
// Show log on click
console.log('click on: ', this)
}
// Create event listener for click event on button
// and pass "handleButtonClick" function as a callback function
button.addEventListener('click', handleButtonClick)
// Or
// Find button in the DOM
const button = document.querySelector('#btn')
// Create event listener for click event on button
// and pass a callback function directly
button.addEventListener('click', function() {
// Show log on click
console.log('click on: ', this)
})
Note: In the first example, you passed the “handleButtonClick” function by name, without parentheses. This means that you are passing the function object itself. If you passed that function with parentheses that would mean you are invoking the function right away and passing the result of executing that function.
Returning functions
Another thing you can do with functions is that you can return them from other functions. This is something expected, since functions are a type of objects, and you can return objects in JavaScript. This can be useful when you want to use function to create templates for new functions.
// Create function that returns function
// a template for new functions
function changeText(word, replacement, text) {
// Return a new function
// that will be later assigned to a variable
return function(text) {
return text.replace(word, replacement)
}
}
// Create new function for changing text
// to positive mood using the "changeText" function
// This invokes and "changeText" function
// and stores returned function in a variable
const makePositive = changeText(/bad/g, 'good')
// Create new function for changing text
// to negative mood using the "changeText" function
// This invokes and "changeText" function
// and stores returned function in a variable
const makeNegative = changeText(/good/g, 'bad')
// Call the "makePositive" function
// This invokes the returned function
// stored in the variable after calling
// the "changeText" function
makePositive('Everything that happened is bad and everything that will happen is also bad.')
// 'Everything that happened is good and everything that will happen is also good.'
// Call the "makePositive" function
// This invokes the returned function
// stored in the variable after calling
// the "changeText" function
makeNegative('Everything that happened is good and everything that will happen is also good.')
// 'Everything that happened is bad and everything that will happen is also bad.'
High-order functions: The basics
You may wonder what those three things have to do with high-order functions. The first one about variables not much. However, the second and third, passing functions as arguments and returning functions, a lot. Here is the thing, high-order functions are functions that take another function as an argument, and/or returns another function.
In all examples, where you were passing a function as an argument, or returning a function, you were actually working with high-order functions. You probably expected something more complex than this. Especially due to how many JavaScript developers talk about high-order functions. However, it is really that simple.
// High-order function no.1:
// Function that takes a function as a argument
function myHighOrderFuncOne(myFunc) {
// some code
}
// High-order function no.2:
// Function that returns a function
function myHighOrderFuncTwo() {
// some code
// Return a function
return function() {
// some code
}
}
// High-order function no.3:
// Function that takes a function as a argument
// and also returns a function
function myHighOrderFuncThree(myFunc) {
// some code
// Return a function
return function() {
// some code
}
}
High-order functions vs first order functions
In JavaScript, there are two types of functions, high-order functions and first order functions. The only difference between these two is that first order functions don’t take a function as an argument and/or don’t return a function.
// Example of high-order function:
function myHighOrderFunc(myFunc) {
// some code
}
// Example of first-order function:
function myFirstOrderFunc() {
// some code
}
Native JavaScript high-order functions
One interesting thing about high-order functions is that there are actually some built-in high-order functions in JavaScript. Chances are you may already work with some of them. Event listeners where one of them. These three are used most often. Note: probably for simplicity, these high-order functions are often used with arrow functions.
map()
The first one of built-in high-order function is the map()
. Well, technically it is a method. Anyway, the map()
is a function, or method, you can use to on arrays to iterate over them. During this iteration, you can work with individual items inside the array. The map()
takes one argument, a function that will be used as a callback.
This callback function is what allows you to work with items inside an array. During every iteration one item is passed into this callback function, along with item’s index and the whole source array.
// Create an array of numbers
const myArray = [1, 5, 7, 9]
// map example no.1: using regular function
// Create callback function for map()
function toThirdPower(item /* index, array */) {
// Raise the item to the third power
return item ** 3
}
// Use map to iterate over items in myArray array
// passing the toThirdPower() function as a callback function
myArray.map(toThirdPower)
// [ 1, 125, 343, 729 ]
// map example no.2: using anonymous function
myArray.map(function(item /* index, array */) {
// Raise the item to the third power
return item ** 3
})
// [ 1, 125, 343, 729 ]
// map example no.3: using arrow function
myArray.map((item /* index, array */) => {
// Raise the item to the third power
return item ** 3
})
// [ 1, 125, 343, 729 ]
// Or, a shorter version of arrow function
myArray.map((item) => item ** 3)
Now, let’s take a look how would the same task look like for example with for
loop.
// Alternative to map
// Create an array of numbers
const myArray = [1, 5, 7, 9]
// Prepare new empty array
let myNewArray = []
// Loop through myArray array
for (let idx = 0; idx < myArray.length; idx++) {
// Raise each number the the third power
// and store it inside myNewArray array
myNewArray.push(myArray[idx] ** 3)
}
// Log the value of myNewArray
console.log(myNewArray)
// [ 1, 125, 343, 729 ]
filter()
Another frequently used built-in high-order function is filter()
method. This method helps you iterate over an array and create new array only with items that meet specific criteria. Similarly to map()
, filter()
also takes one argument, the callback function. On every iteration, it passes one item into this callback function.
It also passes the index of the item and the whole source array. The callback function is used to specify the condition, or test, each item has to pass. The result of this test has to be boolean, true
for passing and false
for failing.
// Create an array of strings
const myArray = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
// filter example no.1: using regular function
// Create callback function for filter()
function testTheWord(word /* index, array */) {
// Remove all words which length is not an even number
return word.length % 2 === 0
}
// Use filter to iterate over items in myArray array
// passing the testTheWord() function as a callback function
myArray.filter(testTheWord)
// [ 'Monday', 'Thursday', 'Friday' ]
// filter example no.2: using anonymous function
myArray.filter(function(word /* index, array */) {
// Remove all words which length is not an even number
return word.length % 2 === 0
})
// [ 'Monday', 'Thursday', 'Friday' ]
// filter example no.3: using arrow function
myArray.filter((word /* index, array */) => {
// Remove all words which length is not an even number
return word.length % 2 === 0
})
// [ 'Monday', 'Thursday', 'Friday' ]
// Or, a shorter version of arrow function
myArray.filter((word) => word.length % 2 === 0)
Again, let’s try to execute the same task with for
loop.
// Alternative to map
// Create an array of strings
const myArray = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
// Prepare new empty array
let myNewArray = []
// Loop through myArray array
for (let idx = 0; idx < myArray.length; idx++) {
// Test each string if it's length is an even number
if (myArray[idx].length % 2 === 0) {
// and store it inside myNewArray array
myNewArray.push(myArray[idx])
}
}
// Log the value of myNewArray
console.log(myNewArray)
// [ 'Monday', 'Thursday', 'Friday' ]
reduce()
The last one, reduce()
, is a method that helps you reduce an array to a single item. It takes a callback function as an argument. It passes four arguments in the callback function: the accumulated value, value of current item, index of current item and the whole source Array. You can also provide initial value, as a second argument after the callback function.
// Create an array of test scores
const myArray = [
{
name: 'tom',
score: 42
},
{
name: 'jessica',
score: 78
},
{
name: 'jacob',
score: 92
},
{
name: 'cindy',
score: 38
},
{
name: 'timothy',
score: 83
}
]
// reduce example no.1: using regular function
// Create callback function for reduce()
function sumScores(acc, curVal) {
// Sum all scores by adding
// the value of score in each object
// to the accumulated value
return acc + curVal.score
}
// Use reduce to iterate over items in myArray array
// passing the sumScores() function as a callback function
// and setting the initial value to 0
myArray.reduce(sumScores, 0)
// 333
// reduce example no.2: using anonymous function
myArray.reduce(function(acc, curVal) {
// Sum all scores
return acc + curVal.score
}, 0) // set the initial value to 0
// 333
// reduce example no.3: using arrow function
myArray.reduce((acc, curVal) => {
// Sum all scores
return acc + curVal.score
}, 0) // set the initial value to 0
// 333
// Or, a shorter version of arrow function
myArray.reduce((acc, curVal) => acc + curVal.score, 0)
// 333
One more time trying for
loop instead of reduce()
method.
const myArray = [
{
name: 'tom',
score: 42
},
{
name: 'jessica',
score: 78
},
{
name: 'jacob',
score: 92
},
{
name: 'cindy',
score: 38
},
{
name: 'timothy',
score: 83
}
]
// Prepare variable for total
let total = 0
// Loop through myArray array
for (let idx = 0; idx < myArray.length; idx++) {
// and store it inside myNewArray array
total += myArray[idx].score
}
// Log the value of total
console.log(total)
// 333
Conclusion: Higher-order Functions in JavaScript Made Simple
In this article, you learned about how JavaScript treats functions and what it means. You’ve also learned that you can pass functions as arguments and return them from other functions. Next, you’ve learned about what higher-order functions are, what distinguishes them from first order functions, and how they work.
Lastly, you’ve learned about built-in JavaScript high-order functions and how to use them. These were map()
, filter()
and reduce()
. I hope that this article helped you understand high-order functions. With that, thank you for your time.
If you liked this article, please subscribe so you don't miss any future post.
If you'd like to support me and this blog, you can become a patron, or you can buy me a coffee 🙂