Functions that take another function as an argument, or that define a function as the return value, are called higher-order functions.
JavaScript can accept higher-order functions. This ability to handle higher-order functions, among other characteristics, makes JavaScript one of the programming languages well-suited for functional programming.
JavaScript Treats Functions as First-Class Citizens
Table of Contents
You may have heard that JavaScript functions are first-class citizens. This means functions in JavaScript are objects.
They have the type Object
, they can be assigned as the value of a variable, and they can be passed and returned just like any other reference variable.
First-class functions give JavaScript special powers and enable us to benefit from higher-order functions.
Because functions are objects, JavaScript is one of the popular programming languages that supports a natural approach to functional programming.
In fact, first-class functions are so native to JavaScript’s approach that I bet you’ve been using them without even thinking about it.
Higher-Order Functions Can Take a Function as an Argument
If you’ve done much JavaScript web development, you’ve probably come across functions that use a callback.
A callback function is a function that executes at the end of an operation, once all other operations are complete.
Usually, we pass this function as an argument last, after other parameters. It’s often defined inline as an anonymous function. Callback functions rely on JavaScript’s ability to deal with higher-order functions.
JavaScript is a single-threaded language. This means only one operation can execute at a time.
To avoid operations blocking each other or the system’s main thread (which would cause deadlock), the engine ensures all operations execute in order. They’re queued along this single thread until it’s safe for another transaction of code to occur.
The ability to pass in a function as an argument and run it after the parent function’s other operations are complete is essential for a language to support higher-order functions.
Callback functions in JavaScript allow for asynchronous behavior, so a script can continue executing other functions or operations while waiting for a result.
The ability to pass a callback function is critical when dealing with resources that may return a result after an undetermined period of time.
This higher-order function pattern is very useful in a web development. A script may send a request off to a server, and then need to handle the response whenever it arrives, without requiring any knowledge of the server’s network latency or processing time.
Node.js frequently uses callback functions to make efficient use of server resources. This asynchronous approach is also useful in the case of an app that waits for user input before performing a function.
Example: Passing an Alert Function to an Element Event Listener
Consider this snippet of simple JavaScript that adds an event listener to a button.
This script uses an anonymous inline function to display an alert.
But it could just as easily have used a separately defined function and passed that named function to the addEventListener
method:
var proveIt = function() {
alert("you triggered " + this.id);
}; document.getElementById("clicker").addEventListener("click", proveIt);
We haven’t just demonstrated higher-order functions by doing this. We’ve made our code more readable and resilient, and separated functionality for different tasks (listening for clicks vs. alerting the user).
How Higher-Order Functions Support Code Reusability
Our little proveIt()
function is structurally independent of the code around it, always returning the id
of whatever element was triggered. This approach to function design is at the core of functional programming.
This bit of code could exist in any context where you display an alert with the id
of an element, and could be called with any event listener.
The ability to replace an inline function with a separately defined and named function opens up a world of possibilities.
In functional programming, we try to develop pure functions that don’t alter external data and return the same result for the same input every time.
We now have one of the essential tools to help us develop a library of small, targeted higher-order functions you can use generically in any application.
Note: Passing Functions vs. Passing the Function Object
Note that we passed proveIt
and not proveIt()
to our addEventListener
function.
When you pass a function by name without parentheses, you are passing the function object itself.
When you pass it with parentheses, you are passing the result of executing that function.
Returning Functions as Results with Higher-Order Functions
In addition to taking functions as arguments, JavaScript allows functions to return other functions as a result.
This makes sense since functions are simply objects. Objects (including functions) can be defined as a function’s returned value, just like strings, arrays, or other values.
But what does it mean to return a function as a result?
Functions are a powerful way to break down problems and create reusable pieces of code. When we define a function as the return value of a higher-order function, it can serve as a template for new functions!
That opens the door to another world of functional JavaScript magic.
Say you’ve read one too many articles about Millennials and grown bored. You decide you want to replace the word Millennials with the phrase Snake People every time it occurs.
Your impulse might be simply to write a function that performed that text replacement on any text you passed to it:
var snakify = function(text) {
return text.replace(/millenials/ig, "Snake People");
};
console.log(snakify("The Millenials are always up to something."));
// The Snake People are always up to something.
That works, but it’s pretty specific to this one situation. Perhaps your patience has also outgrown articles about the Baby Boomers. You’d like to make a custom function for them as well.
But even with such a simple function, you don’t want to have to repeat the code that you’ve written when you can start with a higher-order function instead.
var hippify = function(text) {
return text.replace(/baby boomers/ig, "Aging Hippies");
};
console.log(hippify("The Baby Boomers just look the other way."));
// The Aging Hippies just look the other way.
But what if you decided that you wanted to do something fancier to preserve the case in the original string? You would have to modify both of your new functions to do this.
That’s a hassle, and it makes your code more brittle and harder to read. In situations like this, we can use a higher-order function as a solution.
Building a Template Higher-Order Function
What you really want is the flexibility to be able to replace any term with any other term in a template function, and define that behavior as a foundational function from which you can build new custom functions.
With the ability to assign functions as return values, JavaScript offers up ways to make that scenario much more convenient:
var attitude = function(original, replacement, source) {
return function(source) {
return source.replace(original, replacement);
};
}; var snakify = attitude(/millenials/ig, "Snake People");
var hippify = attitude(/baby boomers/ig, "Aging Hippies"); console.log(snakify("The Millenials are always up to something."));
// The Snake People are always up to something.
console.log(hippify("The Baby Boomers just look the other way."));
// The Aging Hippies just look the other way.
What we’ve done is isolate the code that does the actual work into a versatile and extensible attitude
function. It encapsulates all of the work needed to modify any input string using the original phrase as the initial value, and output a replacement phrase with some attitude.
What do we gain when we define this new function as a reference to the attitude
higher-order function, pre-populated with the first two arguments it takes? It allows the new function to take whatever text you pass it and use that argument in the return function we’ve defined as the attitude
function’s output.
JavaScript functions don’t care about the number of arguments you pass them.
If the second argument is missing, it will treat it as undefined. And it will do the same when we opt not to provide a third argument, or any number of additional arguments, too.
Further, you can pass that additional argument in later. You can do this when you’ve defined the higher-order function you wish to call, as just demonstrated.
Simply define it as a reference to a function a higher-order function returns with one or more arguments left undefined.
Go over that a few times if you need to, so you fully understand what’s happening.
We’re creating a template higher-order function that returns another function. Then we’re defining that newly returned function, minus one attribute, as a custom implementation of the template function.
All the functions you create this way will inherit the working code from the higher-order function. However, you can predefine them with different default arguments.
You’re Already Using Higher-Order Functions
Higher-order functions are so basic to the way JavaScript works, you’re already using them.
Every time you pass an anonymous or callback function, you’re actually taking the value that the passed function returns, and using that as an argument for another function (such as with arrow functions).
Developers become familiar with higher-order functions early in the process of learning JavaScript. It’s so inherent to JavaScript’s design that the need to learn about the concept driving arrow functions or callbacks may not arise until later on.
The ability to assign functions that return other functions extends JavaScript’s convenience. Higher-order functions allow us to create custom-named functions to perform specialized tasks with shared template code from a first-order function.
Each of these functions can inherit any improvements made in the higher-order function down the road. This helps us avoid code duplication, and keeps our source code clean and readable.
If you ensure your functions are pure (they don’t alter external values and always return the same value for any given input), you can create tests to verify that your code changes don’t break anything when you update your first-order functions.
Conclusion
Now that you know how a higher-order function works, you can start thinking about ways you can take advantage of the concept in your own projects.
One of the great things about JavaScript is that you can mix functional techniques right in with the code you’re already familiar with.
Try some experiments. Even if you start by using a higher-order function for the sake of it, you’ll become familiar with the extra flexibility they provide soon enough.
A little work with higher-order functions now can improve your code for years to come.