Closure in JavaScript
One of JavaScript’s most powerful features is the availability of closures. To understand closure, we need knowledge of first-class functions, execution context, execution lexical environment, and scope chain, which we have learned before. While executing a function, in Function Execution Context, if a variable is not found within its own lexical environment it goes down to its outer lexical environment to find the variable definition. So, while executing the function, the execution context encloses its outer environment’s variables along with local variables. This phenomenon of closing in all the variables that it is supposed to have access to is called closure.
Lets see an example to show how closure works.
1 function greet(greeting){
2 return function(name){
3 console.log(`${greeting} ${name}`);
4 }
5 }
6
7 var greetHi=greet('Hi');
8 greetHi('tanveer') // output : "Hi tanveer"
In this example, when greet
function starts executing, an execution context is created and initialized greeting
variable with ‘Hi’. Then it returns a function that logs both greeting
and name
variables. After the execution, the current execution context pops out from the execution stack.
In the next line when the returned function starts executing, another execution context is created and initializesname
variable with ‘tanveer’. In line 3, when we are logging the variables it logs ‘Hi Tanveer’. How did javascript find the value of greeting
variable? we have only name
variable in the current executing context. greeting
variable existed in the parent execution context’s lexical environment. But with the execution of greet
function, its execution context is popped off the execution stack. That's where the role of closure comes into play. Even if the greet
function finished its execution, the ‘greeting’ variable is still in the memory and the execution context of greetHi
function still has a reference to greeting
variable. That's because the JavaScript engine keeps executing function's scope chain intact by creating a closure.
Let's visualize this with an image
In this particular example, the scope is called a function scope, because the variable is accessible and only accessible within the function body where it’s declared. before ES6 JavaScript only had two kinds of scopes: function scope and global scope. In ES6, JavaScript introduced the let
and const
declarations which are block scoped. block-scoped variables are created in JavaScript by creating a temporal dead zone. In addition, ES6 introduced modules, which introduced another kind of scope. Closures are able to capture variables in all these scopes.
Practical Closure
Using private variables and methods:
In JavaScript, we can use private variables and methods using closures. The example below shows the use of private variables with closure.
// Define the closure
var rentPrice = function(initialRent) {
var rent = initialRent;
// Define private variables for
// the closure
return {
getRent: function() {
return console.log(rent);
},
incRent: function(amount) {
rent += amount;
console.log(rent);
},
decRent: function(amount) {
rent -= amount;
console.log(rent);
}
}
}
var Rent = rentPrice(8000);
// Access the private methods
Rent.incRent(2000);
Rent.decRent(1500);
Rent.decRent(1000);
Rent.incRent(2000);
Rent.getRent();
Live binding using modules:
// myModule.js
let x = 5;
export const getX = () => x;
export const setX = (val) => {
x = val;
}
Here, the module exports a pair of getter-setter functions, which close over the module-scoped variable x
. Even when x
is not directly accessible from other modules, it can be read and written with the functions.
import { getX, setX } from "./myModule.js";
console.log(getX()); // 5
setX(6);
console.log(getX()); // 6
Closures can close over imported values as well, which are regarded as live bindings, because when the original value changes, the imported one changes accordingly.
// myModule.js
export let x = 1;
export const setX = (val) => {
x = val;
}
// closureCreator.js
import { x } from "./myModule.js";
export const getX = () => x; // Close over an imported live binding
// otherfile.js
import { getX } from "./closureCreator.js";
import { setX } from "./myModule.js";
console.log(getX()); // 1
setX(2);
console.log(getX()); // 2
Conclusion
Closures are one of those subtle concepts in JavaScript that are difficult to grasp at first. But once you understand them, you realize that things could not have been any other way.