JavaScript uses the Lexical Scope to resolve the variable names when we create functions inside another function. The lexical means that JavaScript determines the parent scope of a function by looking at where we created the function and not where we invoked it. But JavaScript allows passing a function around, which means we may invoke them in a different scope than the original scope. Hence losing the connection to its parent scope. JavaScript solves this problem by creating Closures. A Closure contains function along with reference to its parent scope allowing the function to stay connected with its parent scope.
Table of Contents
Scopes
We can access the JavaScript variables only within the scope in which we declare them. JavaScript creates Global, Module, Function, or Block scope depending on where we declare our variable. We learned all these in our tutorial Scopes in JavaScript.
When we execute a script, the JavaScript creates a Global scope. Global scope is the root scope of all other scopes.
Whenever we invoke a function (or code block ({ }
) since ES6), JavaScript creates a new local scope for them. We call it local scope because variables declared inside them cannot be used outside. The scope act as boundaries for the variables.
JavaScript creates these scope as child scope of the global scope.
In the code below, the variable greetText
in the greeting
is only available inside that function. Trying to access it outside the function results in Uncaught ReferenceError: greetText is not defined
error.
1 2 3 4 5 6 7 8 9 10 11 | function greeting() { let greetText="Hello" console.log(greetText) } greeting(); console.log(greetText) //Uncaught ReferenceError: greetText is not defined error |
The code above creates two scopes. global Scope when the script starts to execute and function scope when we invoke the greeting
function.
Similarly, the code blocks also create a new scope. We cannot access the greetText
declared inside the code block outside the code block.
1 2 3 4 5 6 7 8 9 | { let greetText="Hello" console.log(greetText) } console.log(greetText) //Uncaught ReferenceError: greetText is not defined error |
Note that variables declared with var
keyword ignores the code block. Hence the following code does not throw any errors.
1 2 3 4 5 6 7 8 9 | { var greetText="Hello" //var ignores the code block console.log(greetText) } console.log(greetText) //Hello |
Any variable declared outside the code block or function belongs to the global scope. You can access it anywhere in the application.
Also, remember JavaScript creates scopes when we invoke them not when we declare them. In the code below, we invoke greeting
twice. JavaScript creates new scope on each call to greeting
. Both these scopes are independent of each other.
1 2 3 4 5 6 7 8 9 | function greeting() { let greetText="Hello" console.log(greetText) } greeting(); //a new function scope created here greeting(); //another function scope created here. |
Garbage collection
JavaScript automatically allocates memory when it creates variables and frees it when it no longer needs it. This freeing up of the memory happens behind the scene and we call that garbage collection.
In the code below, when we invoke the greeting
function JavaScript creates a new scope for it. The variable greetText
is created inside that scope. Once the function returns, we no longer need the scope & the variable. Hence JavaScript frees up the memory by removing them via garbage collection.
1 2 3 4 5 6 7 8 9 10 | function greeting() { let greetText="Hello" console.log(greetText) } greeting(); //a new function scope created here |
Variable Resolution
When we refer to a variable, JavaScript always starts to look for it in the current scope i.e. scope of the currently executing function or block.
What if it does not find there?. It will look for it in its parent Scope.
If it finds the variable in Parent Scope, then it will return it. It will not search any further.
But if it fails then it will continue to search in its parent scope. It will continue until it reaches the global scope. if it does not find it in the global scope, then it will throw the Uncaught ReferenceError
.
But how does JavaScript determines which is the parent scope of the currently executing function?.
There are two ways in which modern programming languages can determine the parent scope of a function. They are
- Lexical Scope (static Scope)
- Dynamic Scope
The JavaScript uses the lexical scope to determine the parent scope.
Lexical Scope
In Lexical Scope, the child scope accesses the variable defined in the parent scope lexically. The lexically means that JavaScript determines the parent scope by looking at where we created the function and not where we invoked it.
Let us find out what is lexical scope using few code examples.
Lexical Scope Example
JavaScript allows us to create functions inside functions ( nested functions ), block statements inside another block statement, block statements inside a function or functions inside a block statement, etc.
JavaScript creates new scope for every function and code block that it executes. When we create a nested function (or inner function) inside another function (outer function), the scope of the outer function becomes the parent scope of the inner function.
In the following example, when we invoke the greeting
function it creates a new scope. Inside the greeting
functions scope, we have created the sayHi
function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | let greetText="Hello from global" function greeting() { let greetText="Hello" //Creation of sayHi function inside the greeting Scope //Hence the greeting scope is the parent scope of sayHi sayHi= function() { console.log(greetText); } //Invoking sayHi sayHi(); //Hello } //Invoking the greeting function. //A new scope is created for greeting greeting(); |
The rules of lexical scope say that the parent scope of a function is the scope where we create that function. Since we have created the sayHi
function inside the greeting
function, the current scope of greeting
function becomes the parent scope of the sayHi
function.
When we invoke the sayHi
function it creates a new scope. Inside sayHi
function, we try to access the greetText
variable. The variable resolution starts from the sayHi
scope. Since JavaScript does not find it there, it will move up to its parent scope ( i.e greeting
scope). The search ends there as it will find the variable there.
The following example has three functions. funcGrandChild
is inside the funcChild
, which in turn inside the funcParent
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | //Global Variable. Accessible everywhere let x = 10 function funcParent() { let a = 1; funcChild = function () { let b = 2 funcGrandChild = function () { let c = 3 console.log("funcGrandChild",x,a,b,c) } funcGrandChild() console.log("funcChild",x,a,b) } funcChild() console.log("funcParent",x,a) } funcParent(); ***** Result ***** funcGrandChild 10 1 2 3 code1.js:19 funcChild 10 1 2 code1.js:24 funcParent 10 1 |
There are four scopes in the above example code. function scope for each of the three functions plus one global scope. The funcChild
can access the variable a
defined in its parent function i.e. funcParent
. The funcGrandChild
can access the variable b
defined in its parent function i.e. funcChild
. It can also access the variable a
defined in its grandparent function i.e. funcParent
.
But there is a catch
The above are simple examples. We create a function inside another function and then we immediately invoked it.
In JavaScript, we can assign a function to a variable and pass it around. This enables us to invoke the function in a different scope than the original scope in which we created it.
Take a look at the following code
In the code below we have declared the sayHi
function inside the greeting1
. But we have invoked it inside the greeting2
function. Both greeting1
& greeting2
contain the variable greetText
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | let greetText="Hello from global" function greeting1() { let greetText="Hello" // creating the sayHi function. We will not invoke it sayHi= function() { console.log(greetText); } return sayHi; } sayHi=greeting1(); //new scope is created here function greeting2() { let greetText="Hi" sayHi(); //invoking it inside the scope of greeting2 } greeting2(); // new scope is created here |
JavaScript creates a global scope when we execute the above code. The code let greetText="Hello from global"
creates the global variable greetText
because we are inside the global scope.
The greeting1
scope is created when we invoke the greeting1
function sayHi=greeting1();
The greeting1
function creates the variable greetText
and sayHi
function. Hence both will belong to the greeting1
‘s scope.
When we invoke greeting2()
, we create a new scope for greeting2
.
The greeting2
function also creates greetText
variable let greetText="Hi"
. This greetText
will not overwrite the greetText
in the greeting1
as they are part of different scopes.
Now, we invoke the sayHi
from the scope of greeting2
. This will create a new scope for sayHi
function.
The search for the variable greetText
inside the sayHi
function starts from the scope of the sayHi
function. Since we have not declared it there, It looks for it in its parent scope. The parent scope of sayHi
is the greeting1
scope, so the search continues there and it finds the variable and prints “Hello” in the console.
Alternatively, if when we determine the scope based on where we have invoked the function, we call that dynamic scope. In a dynamically scoped language, the value of the greetText
in the sayHi
function will come from the greeting2
function Because that is where we invoked it. Dynamically scoped language determines the scope dynamically at run time and scope may also change from one invocation to another.
Closures
The closure in JavaScript is a combination of the function object and the reference to the parent scope it belongs to. JavaScript creates a closure every time we create a function, at the time of the creation of the function. The closure is what gives the function access to its parent scope.
To understand the closure let us take a look at the sayHi
example from the previous section
We created the sayHi
function in the scope of greeting1
function. The greeting1
function does not invoke the sayHi
but returns it to the caller and then it finishes its execution. When the function returns, we expect JavaScript to run a garbage collection and clean up the memory.
But when we invoke the sayHi
in the scope of greeting2
function, It prints the value of greetText
from the greeting1
function (function in which we created it) and not from the greeting2
function (function in which we invoked it).
Hence it is clear that although the greeting1
has finished executing, its variable greetText
is still available to sayHi
function. This means JavaScript has not removed the scope of greeting1
from the memory.
This is because JavaScript creates a closure when we create the function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | let greetText="Hello from global" function greeting1() { let greetText="Hello" //function created here. Hence it value of greetText from this scope sayHi= function() { console.log(greetText); } return sayHi //returning sayHi and not invoking it } funcSayHi = greeting1(); function greeting2() { let greetText="Hi" //Function Invoked in scope of greeting2. //But it is still connected to the scope of greeting1 via Closures funcSayHi() } greeting2() //Hello |
How Closures work
JavaScript creates a closure every time we create a function, at the time of the creation of the function.
The functions are objects in JavaScript. Just like any other object, you can store them in a variable. So when we create a function, it creates a variable sayHi
and stores the code of the function there. The function also has a hidden property named [[Environment]]
. We cannot access it from the code. JavaScript uses it to store the reference to the current executing parent scope of the function.
For Example, in the code above invoking the sayHi = greeting1();
creates a new scope. The code inside the greeting1
runs in that new scope. So when we create the sayHi
function, JavaScript saves the reference to the greeting1
along with the function in the sayHi
variable
Hence a function always carries a reference to its parent scope. and stay connected to their parent scope always. Since the function carries a reference to its parent scope, JavaScript will not be able to clean up the parent scope. Parent scope stays in memory as long there is a function that has a reference to it.
When we invoke a function, the Javascript first searches for the variables in the scope of the function itself. If it does not find it then it will look for it in the parent scope. To determine the parent scope it will always use the closure and not the current executing scope.
Closure Examples
The Closures do not copy the parent scope, but stores the reference to it. Hence if anyone modifies a variable in the parent scope, the inner function will see it.
In the following example, we create the startCount
inside the scope of the counter
function. At the time of the creation of the startCount
, the value of the count variable is 10
. We increase the count
and return the startCount
.
When we invoke the startCount
later, it will print the latest value of the count (i.e. 11).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function counter() { let count = 10; startCount= function () { console.log(count); }; count++; return startCount; } showCount= counter() showCount() //11 |
Creating Private Variables
The closure is useful in creating Private variables or hiding implementation detail from the user, which is not possible only using the objects.
In the following example, makeCounter
function creates a new instance of the counterObj
and returns it. The object has two functions increment
& decrement
. Since they are created inside the makeCounter
function, they share the same parent scope.
Both these functions can access the same count
variable from their parent scope because of closure. But we can not access them outside the function. Hence they become private variables.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | function makeCounter(counterName) { let count = 0; let name= counterName let counterObj = { increment: function () { ++count; console.log("%s : %d",name,count) return count }, decrement: function () { --count; console.log("%s : %d",name,count) return count }, } return counterObj } counter1=makeCounter("Counter1") counter1.increment() //Counter1 : 1 counter1.increment() //Counter1 : 2 counter1.increment() //Counter1 : 3 counter1.decrement() //Counter1 : 2 counter1.decrement() //Counter1 : 1 |
Here we make two calls to the makeCounter
. Each call to makeCounter
function to creates a new closure
and returns the new counter object. Both the counters run independently of each other.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | counter1 = makeCounter("Counter1") counter2 = makeCounter("Counter2") counter1.increment() //Counter1 : 1 counter1.increment() //Counter1 : 2 counter1.increment() //Counter1 : 3 counter2.increment() //Counter2 : 1 counter2.decrement() //Counter2 : 0 counter2.increment() //Counter2 : 1 counter2.increment() //Counter2 : 2 counter1.decrement() //Counter1 : 2 counter1.decrement() //Counter1 : 1 |
Closures inside loops
In the following example, the makeFunctions
creates an anonymous function and pushes it to the results array inside a For loop.
The code blocks ( { }
) in JavaScript creates new scope (block scope). Each iteration of the For loop creates a new scope and they become the parent scope of the anonymous function. Hence we end up with three anonymous functions each with the separate block scopes as their parent.
But all three block scopes share the makeFunctions
as their parent scope as they are created inside the makeFunctions scope.
When you run executeFunctions
you will see the output as 2, 2, 2
instead of 1, 2, 3
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | function makeFunctions() { var result=[]; for (let i = 0; i < 3; i++) { var item = i; //annonymous function is created here. Under the block scope of For Loop result.push( function() {console.log(item)} ); } return result; } function executeFunctions() { var result = makeFunctions(); for (var j = 0; j < result.length; j++) { //function invoked here result[j](); } } executeFunctions(); //**** Result *****/ //2 //2 //2 |
Unexpected results due to the fact that the variables declared with var will not become part of the block scope. It will become part of the function scope or global scope. Hence in our example above item
variable is part of the makeFunctions
scope.
So when the for iteration is complete, the value of the item is already 2 in the makeFunctions. When we run the anonymous functions they print that.
We can solve this problem using let
instead of var
, which is block scoped.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function makeFunctions() { var result=[]; for (let i = 0; i < 3; i++) { let item = i; //annonymous function is created here. Under the scope of BuildList result.push( function() {console.log(item)} ); } return result; } .. .. |
Closures in Callback
Event callbacks are one of the most commonly used use cases for closures. In the following code, we register an anonymous callback function. JavaScript creates a closure around it and allows us to access the clicked
variable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <!doctype html> <html> <head> <title> Functions as callbacks </title> <meta charset="utf-8"> </head> <body> <button id="buttonRef">Click Me </button> </body> <script> let buttonRef = document.getElementById("buttonRef") let clicked=0 buttonRef.addEventListener("click", function() { clicked++; buttonRef.innerHTML="Clicked "+clicked }, false); </script> |