Callbacks and closures are used frequently in JavaScript. Callbacks are functions that are passed into another function as an argument. Closures are functions that are nested in other functions, and it’s often used to avoid scope clash with other parts of a JavaScript program.
Callbacks
Functions in JavaScript are objects. Like any other object, you can pass them in as a parameter. Therefore, in JavaScript we can pass in a function as an argument of another function. This is called a callback function. Functions can also be returned as the result of another function. Callback functions can be used for synchronous code and asynchronous code.
Passing in a function as a parameter of another function is different from calling a function. When we call a function, we add parentheses to the end of the function possibly with arguments inside. Functions that are called run immediately. When functions are passed in as an argument, they aren’t called immediately. They’re called when the function that you passed the callback function into calls it. For example, if we have:
element.addEventListener('click', handleClick);
handleClick
isn’t called immediately. It’s called when a click event is triggered by the user.
We can write our own function that takes a callback function as an argument by adding a parameter that takes a function. For example, we can write:
const calculate = (x, y, callbackFn)=>{
return callbackFn(x, y);
})
where callbackFn
is a function parameter that runs inside the calculate
function. If we want to use the calculate
function to calculate something, then we write:
const result = calculate(1, 2, (x, y)=>{
return x + y;
});
result
would is 3 above since we added the 2 numbers x
and y
in the callback function that we passed in. We can also pass in a named function as a callback function:
const add = (x, y) => x + y;
const result = calculate(1, 2, add);
We would get the same result. Also, if our function takes callback function, we can take in any other function as an argument:
const add = (x, y) => x + y;
const subtract = (x, y) => x - y;
const multiply = (x, y) => x * y;
const divide = (x, y) => x / y;
const sum = calculate(1, 2, add); // 3
const difference = calculate(1, 2, subtract); // -1
const product = calculate(1, 2, multiply); // 2
const quotient = calculate(1, 2, divide); // 0.5
As we can see, callback functions make our code more flexible as we can pass in whatever function we want as a callback function. Also, named functions are easier to read since we separated the callback function from the arguments. They can also be reused in other places easily.
However, we didn’t check that the callbackFn
parameter is actually a function. This is going to be an issue if someone passes in something that isn’t a function. To check that it’s actually a function, we use the typeof
operator, as follows:
const calculate = (x, y, callbackFn)=>{
if (typeof callbackFn === 'function'){
return callbackFn(x, y);
}
return 0;
})
To use callback functions in our web page, we put the following in index.html
:
<html>
<head>
<title>Calcuate</title>
<link href="styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<p>
The sum of 1 and 2 is <span id='sum'></span>
</p> <p>
The difference of 1 and 2 is <span id='difference'></span>
</p> <p>
The product of 1 and 2 is <span id='product'></span>
</p> <p>
The quotient of 1 and 2 is <span id='quotient'></span>
</p> <p>
The modulus of 1 and 2 is <span id='modulus'></span>
</p>
<script src="script.js"></script>
</body>
</html>
Then in script.js
we put:
const calculate = (x, y, callbackFn) => {
return callbackFn(x, y);
};
const add = (x, y) => {
return x + y;
}
const subtract = (x, y) => {
return x - y;
}
const multiply = (x, y) => {
return x * y;
}
const divide = (x, y) => {
return x / y;
}
const mod = (x, y) => {
return x % y;
}
window.onload = ()=>{
const sum = calculate(1, 2, add);
const difference = calculate(1, 2, subtract);
const product = calculate(1, 2, multiply);
const quotient = calculate(1, 2, divide);
const modulus = calculate(1, 2, mod);
document.getElementById('sum').innerHTML = sum;
document.getElementById('difference').innerHTML = difference;
document.getElementById('product').innerHTML = product;
document.getElementById('quotient').innerHTML = quotient;
document.getElementById('modulus').innerHTML = modulus;
}
Closures
A closure is a local variable or function that’s used by another function and the references to the function returned to the function. That is, we return a function in an outer function that references to the local variables of the outer function. This is possible if we have functions nested in another function and returned as a reference. In the inner function, we can use the variables of the outer function. Because of the scoping of local variables, inner functions can access the variables of the outer function. When we return the inner function in the outer function, the references to the local variables of the outer function are still referenced in the inner function.
For example, we can write the following code to return a function from an outer function:
const hello = (name)=>{
const greeting = `Hello ${name}`;
const greet = () => alert(greeting);
return greet;
}
const helloJane = hello('Jane');
helloJane();
helloJane
is a function that’s returned by hello
. Note that we still reference the greeting
variable in the helloJane
function. We get an alert box that says ‘Hello Jane’ in it. The local variable greeting
is still accessible because we passed it into the greet
function. Because the greet
function has the greeting
function passed in. However, the name
parameter isn’t accessible to the outside anymore because we didn’t pass it to the greet
function when we return it. As we can see closures allow us to have private data in our code while keeping access to some public data from the outside.
We can expand the example above to make functions to show any greeting. In index.html
:
<html>
<head>
<title>Calcuate</title>
<link href="styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<p id='hello'></p>
<p id='welcome'></p>
<p id='how-are-you'></p>
<p id='goodbye'></p>
<script src="script.js"></script>
</body>
</html>
In script.js
, we write:
const createGreetingFn = (message)=>{
const greeting = message;
const greet = () => greeting;
return greet;
}
window.onload = ()=>{
const hello = createGreetingFn('Hello');
const welcome = createGreetingFn('Welcome');
const howAreYou = createGreetingFn('How are you?');
const goodBye = createGreetingFn('Goodbye');
document.getElementById('hello').innerHTML = hello();
document.getElementById('welcome').innerHTML = welcome();
document.getElementById('how-are-you').innerHTML = howAreYou();
document.getElementById('goodbye').innerHTML = goodBye();
}
As we can see, we can create multiple functions from the same function that does different things. A function that creates new functions is called a function factory. We created functions that different things from one function. This is another way to make our JavaScript more flexible and let us reuse our code. In the window.onload
function, we called the functions that we created by the createGreetingFn
that we called, which returns the results and then assigned the results to the innerHTML
property of our elements.