In this article, we'll explore the concept of closures in JavaScript. We'll discuss what closures are, how they work, and how they can be used to create powerful and flexible code.
A function declared / defined inside another function has access to the outer function's scope, even after the outer function has returned. This is called a closure.
function outerFunction() {
const outerVariable = 'I am from outer function';
return function () {
console.log(outerVariable);
};
}
const innerFunction = outerFunction();
innerFunction(); // I am from outer functioninnerFunction() still has access to the outerFunction() scope, even after outerFunction() has returned
A lexical environment is the environment in which a piece of code is executed. It consists of the variables that are in scope at that time, as well as a reference to the outer lexical environment. When a function is defined, it captures its lexical environment, creating a closure.
Diagram Loading...
This is what enables closures to work in JavaScript.
Closures enable use to create private variables in JavaScript. That cannot be directly accessed from outside the function.
function createCounter() {
let count = 0;
return {
increment: () => {
count++;
return count;
},
decrement: () => {
count--;
return count;
},
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.count; // undefinedClosures help maintain the execution context of an asynchronous task.
The callback function passed to an asynchronous task has access to the variables in the outer scope, even after the outer function has returned.
function attachEventHandlers(buttonId) {
const button = document.getElementById(buttonId);
button.addEventListener('click', function () {
// buttonId is still accessible here
console.log(`Button ${buttonId} clicked`);
});
}
attachEventHandlers('btn-1');
attachEventHandlers('btn-2');Diagram Loading...
callBack() still has access to the buttonId variable, even after outerFunction() has returned
When using closures, be careful of shared scope issues. If multiple functions share the same outer scope, they will share the same variables.
function createFunctions() {
const functions = [];
for (var idx = 0; idx < 3; idx++) {
functions.push(function () {
console.log(idx);
});
}
return functions;
}
const funcs = createFunctions();
funcs[0](); // Output: 3 (not 0)
funcs[1](); // Output: 3 (not 1)
funcs[2](); // Output: 3 (not 2)In this example, all the functions share the same idx value. Because var keyword is function-scoped instead of block-scoped.
In each iteration of the loop, the same idx variable is reassigned a new value.
for (let idx = 0; idx < 3; idx++) {Switching from var to let fixes the issue.
You can learn more about the differences between var, let, and const in JavaScript in the following article:
The Building Blocks: Variables and Values in JavaScriptWhen using closures, be mindful of memory leaks. If a closure holds a reference to a large object, it can prevent the object from being garbage collected.
function createLargeClosure() {
let largeArray = new Array(1000000).fill(0);
return function () {
console.log("Closure created");
};
}
const largeClosure = createLargeClosure();largeArray var is retained in memory, until the largeClosure func is no longer needed. (eg: by setting the value of largeClosure to null)
Closures are a powerful feature of JavaScript that enable us to create flexible and maintainable code.
By understanding how closures work and how to use them effectively, you can take your JavaScript skills to the next level. Just be mindful of the common pitfalls and best practices.
A function declared / defined inside another function has access to the outer function's scope, even after the outer function has returned. This is called a closure.
1function outerFunction() {
2 const outerVariable = 'I am from outer function';
3
4 return function () {
5 console.log(outerVariable);
6 };
7}
8
9const innerFunction = outerFunction();
10innerFunction(); // I am from outer functioninnerFunction() still has access to the outerFunction() scope, even after outerFunction() has returned
1function outerFunction() {
2 const outerVariable = 'I am from outer function';
3
4 return function () {
5 console.log(outerVariable);
6 };
7}
8
9const innerFunction = outerFunction();
10innerFunction(); // I am from outer functionA lexical environment is the environment in which a piece of code is executed. It consists of the variables that are in scope at that time, as well as a reference to the outer lexical environment. When a function is defined, it captures its lexical environment, creating a closure.
Diagram Loading...
This is what enables closures to work in JavaScript.
Closures enable use to create private variables in JavaScript. That cannot be directly accessed from outside the function.
function createCounter() {
let count = 0;
return {
increment: () => {
count++;
return count;
},
decrement: () => {
count--;
return count;
},
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.count; // undefinedClosures help maintain the execution context of an asynchronous task.
The callback function passed to an asynchronous task has access to the variables in the outer scope, even after the outer function has returned.
1function attachEventHandlers(buttonId) {
2 const button = document.getElementById(buttonId);
3
4 button.addEventListener('click', function () {
5 // buttonId is still accessible here
6 console.log(`Button ${buttonId} clicked`);
7 });
8}
9
10attachEventHandlers('btn-1');
11attachEventHandlers('btn-2');Diagram Loading...
callBack() still has access to the buttonId variable, even after outerFunction() has returned
Closures enable use to create private variables in JavaScript. That cannot be directly accessed from outside the function.
1function createCounter() {
2 let count = 0;
3
4 return {
5 increment: () => {
6 count++;
7 return count;
8 },
9 decrement: () => {
10 count--;
11 return count;
12 },
13 };
14}
15
16const counter = createCounter();
17counter.increment(); // 1
18counter.increment(); // 2
19counter.count; // undefined1function createCounter() {
2 let count = 0;
3
4 return {
5 increment: () => {
6 count++;
7 return count;
8 },
9 decrement: () => {
10 count--;
11 return count;
12 },
13 };
14}
15
16const counter = createCounter();
17counter.increment(); // 1
18counter.increment(); // 2
19counter.count; // undefinedClosures help maintain the execution context of an asynchronous task.
The callback function passed to an asynchronous task has access to the variables in the outer scope, even after the outer function has returned.
1function attachEventHandlers(buttonId) {
2 const button = document.getElementById(buttonId);
3
4 button.addEventListener('click', function () {
5 // buttonId is still accessible here
6 console.log(`Button ${buttonId} clicked`);
7 });
8}
9
10attachEventHandlers('btn-1');
11attachEventHandlers('btn-2');Diagram Loading...
callBack() still has access to the buttonId variable, even after outerFunction() has returned
1function attachEventHandlers(buttonId) {
2 const button = document.getElementById(buttonId);
3
4 button.addEventListener('click', function () {
5 // buttonId is still accessible here
6 console.log(`Button ${buttonId} clicked`);
7 });
8}
9
10attachEventHandlers('btn-1');
11attachEventHandlers('btn-2');When using closures, be careful of shared scope issues. If multiple functions share the same outer scope, they will share the same variables.
function createFunctions() {
const functions = [];
for (var idx = 0; idx < 3; idx++) {
functions.push(function () {
console.log(idx);
});
}
return functions;
}
const funcs = createFunctions();
funcs[0](); // Output: 3 (not 0)
funcs[1](); // Output: 3 (not 1)
funcs[2](); // Output: 3 (not 2)In this example, all the functions share the same idx value. Because var keyword is function-scoped instead of block-scoped.
In each iteration of the loop, the same idx variable is reassigned a new value.
1for (let idx = 0; idx < 3; idx++) {Switching from var to let fixes the issue.
You can learn more about the differences between var, let, and const in JavaScript in the following article:
The Building Blocks: Variables and Values in JavaScriptWhen using closures, be mindful of memory leaks. If a closure holds a reference to a large object, it can prevent the object from being garbage collected.
function createLargeClosure() {
let largeArray = new Array(1000000).fill(0);
return function () {
console.log("Closure created");
};
}
const largeClosure = createLargeClosure();largeArray var is retained in memory, until the largeClosure func is no longer needed. (eg: by setting the value of largeClosure to null)
When using closures, be careful of shared scope issues. If multiple functions share the same outer scope, they will share the same variables.
1function createFunctions() {
2 const functions = [];
3
4 for (var idx = 0; idx < 3; idx++) {
5 functions.push(function () {
6 console.log(idx);
7 });
8 }
9
10 return functions;
11}
12
13const funcs = createFunctions();
14funcs[0](); // Output: 3 (not 0)
15funcs[1](); // Output: 3 (not 1)
16funcs[2](); // Output: 3 (not 2)In this example, all the functions share the same idx value. Because var keyword is function-scoped instead of block-scoped.
In each iteration of the loop, the same idx variable is reassigned a new value.
for (let idx = 0; idx < 3; idx++) {Switching from var to let fixes the issue.
You can learn more about the differences between var, let, and const in JavaScript in the following article:
The Building Blocks: Variables and Values in JavaScript1function createFunctions() {
2 const functions = [];
3
4 for (var idx = 0; idx < 3; idx++) {
5 functions.push(function () {
6 console.log(idx);
7 });
8 }
9
10 return functions;
11}
12
13const funcs = createFunctions();
14funcs[0](); // Output: 3 (not 0)
15funcs[1](); // Output: 3 (not 1)
16funcs[2](); // Output: 3 (not 2)1for (let idx = 0; idx < 3; idx++) {When using closures, be mindful of memory leaks. If a closure holds a reference to a large object, it can prevent the object from being garbage collected.
1function createLargeClosure() {
2 let largeArray = new Array(1000000).fill(0);
3
4 return function () {
5 console.log("Closure created");
6 };
7}
8
9const largeClosure = createLargeClosure();largeArray var is retained in memory, until the largeClosure func is no longer needed. (eg: by setting the value of largeClosure to null)
1function createLargeClosure() {
2 let largeArray = new Array(1000000).fill(0);
3
4 return function () {
5 console.log("Closure created");
6 };
7}
8
9const largeClosure = createLargeClosure();Closures are a powerful feature of JavaScript that enable us to create flexible and maintainable code.
By understanding how closures work and how to use them effectively, you can take your JavaScript skills to the next level. Just be mindful of the common pitfalls and best practices.
1function outerFunction() {
2 const outerVariable = 'I am from outer function';
3
4 return function () {
5 console.log(outerVariable);
6 };
7}
8
9const innerFunction = outerFunction();
10innerFunction(); // I am from outer functionClosures enable use to create private variables in JavaScript. That cannot be directly accessed from outside the function.
function createCounter() {
let count = 0;
return {
increment: () => {
count++;
return count;
},
decrement: () => {
count--;
return count;
},
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.count; // undefinedClosures help maintain the execution context of an asynchronous task.
The callback function passed to an asynchronous task has access to the variables in the outer scope, even after the outer function has returned.
1function attachEventHandlers(buttonId) {
2 const button = document.getElementById(buttonId);
3
4 button.addEventListener('click', function () {
5 // buttonId is still accessible here
6 console.log(`Button ${buttonId} clicked`);
7 });
8}
9
10attachEventHandlers('btn-1');
11attachEventHandlers('btn-2');Diagram Loading...
callBack() still has access to the buttonId variable, even after outerFunction() has returned
Closures enable use to create private variables in JavaScript. That cannot be directly accessed from outside the function.
1function createCounter() {
2 let count = 0;
3
4 return {
5 increment: () => {
6 count++;
7 return count;
8 },
9 decrement: () => {
10 count--;
11 return count;
12 },
13 };
14}
15
16const counter = createCounter();
17counter.increment(); // 1
18counter.increment(); // 2
19counter.count; // undefined1function createCounter() {
2 let count = 0;
3
4 return {
5 increment: () => {
6 count++;
7 return count;
8 },
9 decrement: () => {
10 count--;
11 return count;
12 },
13 };
14}
15
16const counter = createCounter();
17counter.increment(); // 1
18counter.increment(); // 2
19counter.count; // undefinedClosures help maintain the execution context of an asynchronous task.
The callback function passed to an asynchronous task has access to the variables in the outer scope, even after the outer function has returned.
1function attachEventHandlers(buttonId) {
2 const button = document.getElementById(buttonId);
3
4 button.addEventListener('click', function () {
5 // buttonId is still accessible here
6 console.log(`Button ${buttonId} clicked`);
7 });
8}
9
10attachEventHandlers('btn-1');
11attachEventHandlers('btn-2');Diagram Loading...
callBack() still has access to the buttonId variable, even after outerFunction() has returned
1function attachEventHandlers(buttonId) {
2 const button = document.getElementById(buttonId);
3
4 button.addEventListener('click', function () {
5 // buttonId is still accessible here
6 console.log(`Button ${buttonId} clicked`);
7 });
8}
9
10attachEventHandlers('btn-1');
11attachEventHandlers('btn-2');When using closures, be careful of shared scope issues. If multiple functions share the same outer scope, they will share the same variables.
function createFunctions() {
const functions = [];
for (var idx = 0; idx < 3; idx++) {
functions.push(function () {
console.log(idx);
});
}
return functions;
}
const funcs = createFunctions();
funcs[0](); // Output: 3 (not 0)
funcs[1](); // Output: 3 (not 1)
funcs[2](); // Output: 3 (not 2)In this example, all the functions share the same idx value. Because var keyword is function-scoped instead of block-scoped.
In each iteration of the loop, the same idx variable is reassigned a new value.
1for (let idx = 0; idx < 3; idx++) {Switching from var to let fixes the issue.
You can learn more about the differences between var, let, and const in JavaScript in the following article:
The Building Blocks: Variables and Values in JavaScriptWhen using closures, be mindful of memory leaks. If a closure holds a reference to a large object, it can prevent the object from being garbage collected.
function createLargeClosure() {
let largeArray = new Array(1000000).fill(0);
return function () {
console.log("Closure created");
};
}
const largeClosure = createLargeClosure();largeArray var is retained in memory, until the largeClosure func is no longer needed. (eg: by setting the value of largeClosure to null)
When using closures, be careful of shared scope issues. If multiple functions share the same outer scope, they will share the same variables.
1function createFunctions() {
2 const functions = [];
3
4 for (var idx = 0; idx < 3; idx++) {
5 functions.push(function () {
6 console.log(idx);
7 });
8 }
9
10 return functions;
11}
12
13const funcs = createFunctions();
14funcs[0](); // Output: 3 (not 0)
15funcs[1](); // Output: 3 (not 1)
16funcs[2](); // Output: 3 (not 2)In this example, all the functions share the same idx value. Because var keyword is function-scoped instead of block-scoped.
In each iteration of the loop, the same idx variable is reassigned a new value.
for (let idx = 0; idx < 3; idx++) {Switching from var to let fixes the issue.
You can learn more about the differences between var, let, and const in JavaScript in the following article:
The Building Blocks: Variables and Values in JavaScript1function createFunctions() {
2 const functions = [];
3
4 for (var idx = 0; idx < 3; idx++) {
5 functions.push(function () {
6 console.log(idx);
7 });
8 }
9
10 return functions;
11}
12
13const funcs = createFunctions();
14funcs[0](); // Output: 3 (not 0)
15funcs[1](); // Output: 3 (not 1)
16funcs[2](); // Output: 3 (not 2)1for (let idx = 0; idx < 3; idx++) {When using closures, be mindful of memory leaks. If a closure holds a reference to a large object, it can prevent the object from being garbage collected.
1function createLargeClosure() {
2 let largeArray = new Array(1000000).fill(0);
3
4 return function () {
5 console.log("Closure created");
6 };
7}
8
9const largeClosure = createLargeClosure();largeArray var is retained in memory, until the largeClosure func is no longer needed. (eg: by setting the value of largeClosure to null)
1function createLargeClosure() {
2 let largeArray = new Array(1000000).fill(0);
3
4 return function () {
5 console.log("Closure created");
6 };
7}
8
9const largeClosure = createLargeClosure();Closures are a powerful feature of JavaScript that enable us to create flexible and maintainable code.
By understanding how closures work and how to use them effectively, you can take your JavaScript skills to the next level. Just be mindful of the common pitfalls and best practices.
1function createCounter() {
2 let count = 0;
3
4 return {
5 increment: () => {
6 count++;
7 return count;
8 },
9 decrement: () => {
10 count--;
11 return count;
12 },
13 };
14}
15
16const counter = createCounter();
17counter.increment(); // 1
18counter.increment(); // 2
19counter.count; // undefined1function createFunctions() {
2 const functions = [];
3
4 for (var idx = 0; idx < 3; idx++) {
5 functions.push(function () {
6 console.log(idx);
7 });
8 }
9
10 return functions;
11}
12
13const funcs = createFunctions();
14funcs[0](); // Output: 3 (not 0)
15funcs[1](); // Output: 3 (not 1)
16funcs[2](); // Output: 3 (not 2)1function createLargeClosure() {
2 let largeArray = new Array(1000000).fill(0);
3
4 return function () {
5 console.log("Closure created");
6 };
7}
8
9const largeClosure = createLargeClosure();1for (let idx = 0; idx < 3; idx++) {1function createCounter() {
2 let count = 0;
3
4 return {
5 increment: () => {
6 count++;
7 return count;
8 },
9 decrement: () => {
10 count--;
11 return count;
12 },
13 };
14}
15
16const counter = createCounter();
17counter.increment(); // 1
18counter.increment(); // 2
19counter.count; // undefined1function createFunctions() {
2 const functions = [];
3
4 for (var idx = 0; idx < 3; idx++) {
5 functions.push(function () {
6 console.log(idx);
7 });
8 }
9
10 return functions;
11}
12
13const funcs = createFunctions();
14funcs[0](); // Output: 3 (not 0)
15funcs[1](); // Output: 3 (not 1)
16funcs[2](); // Output: 3 (not 2)1function createLargeClosure() {
2 let largeArray = new Array(1000000).fill(0);
3
4 return function () {
5 console.log("Closure created");
6 };
7}
8
9const largeClosure = createLargeClosure();1for (let idx = 0; idx < 3; idx++) {