Lexical Scope & Closures in JavaScript

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.

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.

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.

Note that variables declared with var keyword ignores the code block. Hence the following code does not throw any errors.

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.

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.

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

  1. Lexical Scope (static Scope)
  2. 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.

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.

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.

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.

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).

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.

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.

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.

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.

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

Leave a Comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top