Closure in JavaScript

Tanveer Hasan
3 min readDec 25, 2022

--

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 greetingand 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 greetingvariable? 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

closure

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.

--

--

No responses yet