Understanding Scope and Closures in Javascript

Sagarika Dalai
7 min readMay 1, 2022
Photo by Gabriel Heinzer on Unsplash

Scopes and closures are important in JavaScript. But, they were confusing for me when I first started šŸ˜‡šŸ˜‡. Hereā€™s an explanation of scopes and closures to help you understand what they are.

Having a proper understanding of these concepts will help you to write better, more efficient, and clean code šŸ¤©.

So without further ado, Letā€™s get started šŸ˜€

What is Scope?

The current context of execution. The context in which values and expressions are ā€œvisibleā€ or can be referenced. If a variable or other expression is not ā€œin the current scope,ā€ then it is unavailable for use.

Why is Scope Important?

The main benefit of scope is security. That is, the variables can be accessed from only a certain area of the program. Using the scope, we can avoid unintended modifications to the variables from other parts of the program. The scope also reduces the namespace collisions. That is, we can use the same variable names in different scopes.

Types of Scope

There are three types of scope in JavaScript ā€” 1) Global Scope, 2) Function Scope, and, 3) Block Scope.

1. Global Scope

Any variable thatā€™s not inside any function or block (a pair of curly braces), is inside the global scope. Once youā€™ve declared a global variable, you can use that variable anywhere in your code, even in functions. For example:

Although you can declare variables in the global scope, it is advised not to. This is because there is a chance of naming collisions, where two or more variables are named the same. If you declared your variables with const or let, you would receive an error whenever a name collision happens.

If you are using VS Code and if quick-lint-js is installed then you will see something like this šŸ‘‡šŸ¼

If you declare your variables with var, your second variable overwrites the first one after it is declared like this šŸ‘‡šŸ¼

So, you should always declare local variables, not global variables.

2. Local Scope or Function Scope

Variables declared inside a function is inside the local scope. They can only be accessed from within that function, which means they canā€™t be accessed from the outside code. For example:

In the example abovešŸ‘†šŸ¼, the variable greeting is in the greet scope.

3. Block Scope

ES6 introduced let and const variables, unlike var variables, which can be scoped to the nearest pair of curly braces. That means, they canā€™t be accessed from outside that pair of curly braces. For example:

We can see that var variables can be used outside the block, that is, var variables are not block scoped.

Nested Scope

Just like functions in JavaScript, a scope can be nested inside another scope. For example:

Closures

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer functionā€™s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation timeā€¦..

Hmm confused right..šŸ¤©šŸ¤© I knowā€¦.I will explain with an example, Now answer this question.

You have already console logged the code and you got the answer, but how?

First, letā€™s make sure how many functions are there in the above code? We can see that the keyword function is used in two places in the code above, so there are two functions in the code above, namely the first line function foo(a,b) { and the fourth line foo: function(c){. And these two functions have the same name.

Second question: Which function is called by foo (c, a) on line 5? If youā€™re not sure, letā€™s take a look at a simpler example:

Why late open your browser(chrome), and open developer tools(use Option + āŒ˜ + J (on macOS), or Shift + CTRL + J (on Windows/Linux). Now, copy paste the code and run you see something like this.

This is because the upper scope of the obj.fn() method is global and the fn method inside obj cannot be accessed. Going back to our previous example, by the same logic, when we call foo(c, a), weā€™re actually calling the foo function on the first line. And when we call res.foo(1), which foo is called? Obviously, the foo function on the 4th line is called.

Because the two foo functions do not work the same way, we can change the name of one of them to bar to make it easier for us to understand the code.

This change will not affect the final result but will make it easier for us to understand the code. Try this tip if you meet a similar question in the future.

Each time a function is called, a new scope is created, so we can draw the diagram to help us understand the logic of how the code works.

When we execute let res = foo(0);, weā€™re actually executing foo(0, undefiend). At this point, a new scope is created in the program, in the current scope, a=0, b=undefined. So the diagram that Iā€™ve drawn looks something like this.

Then console.log(b) will be executed, so the first time it prints out ā€˜undefinedā€™ in the console. Then res.bar(1) is executed, creating a new scope where c=1:

And then foo(c, a) is called again from the above function, which is actually foo(1, 0), and the scope looks like this:

In the new scope, the value of a is 1 and the value of b is 0, so the console will print out the 0.

Again, res.bar(1) is executed next. Notice that res.bar(2) and res.bar(1) are parallel relations, so we should draw the scope diagram like this:

So in this code, the console prints out the value 0 too. The same goes for the process that executes res.bar(3), and the console still prints a 0. So the end result of the code above is:

In fact, the above question can be changed in other ways. For example, it can be changed into the following:

Before we solve this question, the first thing we need to do is distinguish between the two different foo functions, so the above code can be changed to look like this:

When it executes foo(0), the scope is the same as before, and then the console will print out ā€˜undefinedā€™.

Then execute .bar(1) to create a new scope. This parameter 1 is actually the value of c.

Then the .bar(1) method calls foo(c, a) again, which is actually foo(1, 0). Parameter 1 here is actually going to be the value of a in the new scope, and 0 is going to be the value of b in the new scope.

So the console then prints out the value of b, which is 0. .bar(2) is called again, with 2 as the value of c in the new scope:

And then .bar(2) calls foo(c, a), which is actually foo(2, 1), where 2 is the value of a in the new scope, and 1 is the value of b in the new scope.

So the console then prints out the value of b, which is 0.

And then it will execute .bar(3), the process is the same as before, so Iā€™m not going to expand the description, in this step the console prints out the 2.

As mentioned above, the final result of the code running is:

Well, after a long journey, we finally got the answer.

This is it hope you enjoyed while reading thisā€¦šŸ˜€šŸ˜€.

--

--