What is a closure in JavaScript? (with examples)
By the end of this article you should understand the following quote:
A closure is the grouping of a function and a reference to that function's outer scope.
This means that a function can access stuff outside of it. For instance:
var a = 10;
function test() {
console.log(a);
}
test(); // Logs 10.
In the code above, the function test
can access the variable a
that is outside it.
But is it referencing the variable or copying it? Let's test it!
var a = 10;
function f() {
console.log(a);
}
f(); // Logs 10
a = 11;
f(); // Logs 11
In the code above, calling the f
function after changing the variable a
shows a different value. That means the variable a
is not copied! The variable is accessed via a reference that is in the outer scope.
What is the outer scope?
You can imagine the outer scope as a list of identifiers (e.g. functions, variables, parameters). In the example above, the a
is an identifier part of the outer scope.
The outer scope can also be called lexical environment.
Why use closures?
You use closures if you want to have private state associated with one or more functions.
If you know classes
, it provides similar advantages as classes
but it doesn't need to use the keyword this
.
Let's see an example.
function carFactory(description) {
function getDescription() {
return `The car is a ${description}.`;
}
return getDescription;
}
const maryCar = carFactory('Audi R8');
const janeCar = carFactory('Seat Ibiza');
console.log(maryCar()); // Logs "The car is a Audi R8."
console.log(janeCar()); // Logs "The car is a Seat Ibiza."
Notice in the example above, a function returns a function.
The parameter description
is the private state. You can't reach it from outside the makeCar
function.
Let's make a better car factory.
function carFactory(fuel, description) {
let tankQuantity = fuel;
function info() {
return `The car is a ${description}.`;
}
function tank() {
return `The car has ${tankQuantity}l of fuel.`;
}
function fillTank(value) {
tankQuantity = tankQuantity + value;
}
return {
info,
tank,
fillTank,
};
}
const maryCar = carFactory(2, 'Audi R8');
console.log(maryCar.info()); // Logs "The car is a Audi R8."
console.log(maryCar.tank()); // Logs "The car has 2l of fuel."
maryCar.fillTank(8);
console.log(maryCar.tank()); // Logs "The car has 10l of fuel."
This is quite nice! It's like a class without the this
keyword. And this
is responsible for so many bugs and confusion!
In the code above, when you call the carFactory
function, you return an object that has 3 functions: info
, tank
and fillTank
.
Notice if you call fillTank
with a value, it will add that value to the private variable tankQuantity
.
Should I use classes or closures?
So if classes and closures are similar, when should I use closures? Does it even make sense to learn it?
In JavaScript, closures are simpler than classes. But classes are faster than closures and use less memory.
My rule of thumb is to use closures unless I need more speed or memory. Performance is rarely more important than readability.
readability > performance
Scope chain
If you have a function inside a function inside a function, you create a scope chain. The inner functions only have access to the outer functions.
Every closure is going to have 3 scopes:
- local scope
- outer functions scope
- global scope
For instance:
var out = 'out';
function fnA() {
var a = 'a';
console.log(out, a); // Logs "out a"
// console.log(out, a, b, c); // error, can't access b or c
fnB();
function fnB() {
var b = 'b';
console.log(out, a, b); // Logs "out a b"
// console.log(out, a, b, c); // error, can't access c
fnC();
function fnC() {
var c = 'c';
console.log(out, a, b, c); // Logs "out a b c"
}
}
}
fnA();
Each returned function creates new internal state
You've seen that closures let us create internal state. But is the internal state shared?
function makeFn() {
let counter = 0;
return {
log: function () {
console.log(counter);
},
increment: function () {
counter++;
},
};
}
const o1 = makeFn();
const o2 = makeFn();
o1.increment();
o1.log(); // 1
o2.increment();
o2.log(); // 1
// Object o1 did not change when o2 was incremented.
o1.log(); // 1
Yes! Each return function creates new internal state.
Every time we call the makeFn
, we create new internal state.
A new set of variables is created every time a function is called.
Closures inside loops
What do you expect to be the result of the function below?
function makeArrayFn() {
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(i);
}
return arr;
}
console.log(makeArrayFn()); // [0, 1, 2]
It shows [0, 1, 2]
. It makes sense. You loop through the variable i
and it increments until it stop when it reaches 3.
What about the code below?
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(function inner() {
// i is inside the closure created by inner
// and referencing the hoisted i
return i;
});
}
console.log(arr.map((f) => f())); // [3, 3, 3]
It shows [3, 3, 3]
! How can it be? 2 reasons.
First reason is the variable i
is hoisted out of the loop. So our function is basically the same as:
var arr = [];
var i = 0; // i was "hoisted" to the top of the function!
for (; i < 3; i++) {
arr.push(function inner() {
return i;
});
}
console.log(arr.map((f) => f())); // [3, 3, 3]
The second reason is because the inner
function is referencing the
i
after the loop ran. So at this moment, its value is 3
.
If we pushed the variable into the array, it would do a copy of the variable. But because inner
creates a closure, the i
inside it is referencing (instead of copying) the last value in the i
variable! 😱
How do we fix this? There are many ways.
Fix 1
Simplest one, use let
instead of var
:
let arr = [];
for (let i = 0; i < 3; i++) {
arr.push(function inner() {
// on every loop iteration, i is created as a new variable
// it creates a fresh binding with its own scope (block scope)
return i;
});
}
console.log(arr.map((f) => f())); // [0, 1, 2]
On the example above, on each loop iteration, the variable i
is like a new variable, so the function inner
is going to reference that variable.
The i
is scoped to the for loop and not to the full function like it was happening when using var
.
Fix 2
Use an external function:
var arr = [];
function outer(val) {
return val;
}
for (var i = 0; i < 3; i++) {
arr.push(outer(i));
}
console.log(arr); // [0, 1, 2]
The outer
function creates a new scope for the parameter val
. So the function outer
will be referencing the parameter val
instead of the variable i
.
Fix 3
Another alternative is to use an IIFE (immediately executed function expression):
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(
(function inner(val) {
// the function receives an argument i
// and it creates a new scope (the inner function parameter)
return val;
})(i)
);
}
console.log(arr); // [0, 1, 2]
With the IIFE, the inner
function creates a new scope for the parameter val
. So the function inner
will be referencing the parameter val
instead of the variable i
.
This fix works the same way as the previous fix. By using the parameter as the scope.
More examples
With anonymous function expressions (like before but without the name):
var arr = [];
for (let i = 0; i < 3; i++) {
arr.push(function () {
return i;
});
}
console.log(arr.map((f) => f())); // [0, 1, 2]
With arrow functions:
var arr = [];
for (let i = 0; i < 3; i++) {
arr.push(() => i);
}
console.log(arr.map((f) => f())); // [0, 1, 2]
Example with setTimeout:
// with var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // logs 3 3 3
}
// with let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // logs 0 1 2
}
Questions
What's the output?
var x = 1;
function f() {
console.log(x);
}
f();
What's the console output?
function f() {
const n = 2;
return function g(arg1) {
return n + arg1;
};
}
f()(6);
What's the console output?
function fn() {
let n = 2;
return {
f: () => n++,
g: () => n,
};
}
const o1 = fn();
const o2 = fn();
o1.f();
console.log(o2.g());
What's the console output?
function fn()
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(i);
}
return arr;
}
console.log(fn());
What's the console output?
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(function fn() {
return i;
});
}
console.log(arr.map((f) => f()));
What's the console output?
let arr = [];
for (let i = 0; i < 3; i++) {
arr.push(function fn() {
return i;
});
}
console.log(arr.map((f) => f()));
What's the console output?
var arr = [];
function fn(val) {
return val;
}
for (var i = 0; i < 3; i++) {
arr.push(fn(i));
}
console.log(arr);
What's the console output?
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(
(function fn(val) {
return val;
})(i)
);
}
console.log(arr);
What's the console output?
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(function () {
return i;
});
}
console.log(arr.map((f) => f()));
What's the console output?
var arr = [];
for (let i = 0; i < 3; i++) {
arr.push(() => i);
}
console.log(arr.map((f) => f()));
What's the output?
function f() {
console.log(x);
}
var x = 1;
f();
What's the console output?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
What's the console output?
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
What's the console output?
var v = 'a';
function f() {
return v;
}
f();
v = 'b';
console.log(f(v));
What's the outer scope?
What's the console output?
function f(val) {
return function g() {
return val + val;
};
}
const v1 = f(1);
const v2 = f('a');
console.log(v1(), v2());
What's the console output?
function fn(arg1) {
let v = arg1;
function f() {
return v;
}
function g() {
v++;
}
return { k1: f, k2: g };
}
const obj = fn(1);
obj.k2();
const res = obj.k1();
console.log(res);
What's the console output?
function fn(arg1) {
let v = arg1;
return {
k1: () => v++,
k2: () => ++v,
};
}
const obj = fn(1);
console.log(obj.k1());
What's the console output?
function fn(arg1) {
let v = arg1;
return {
k1: () => v++,
k2: () => ++v,
};
}
const obj = fn(3);
console.log(obj.k2());
What's the console output?
function f(arg1) {
return function g(arg2) {
return function h(arg3) {
return arg1 + arg2 + arg3;
};
};
}
f(2)(2)(2);