Understanding Lexical Scope and Closure in JavaScript

Understanding Lexical Scope and Closure in JavaScript

Delving into the Inner Workings of Lexical Scope and Closures to Master Advanced JavaScript Programming

Closures were a rather daunting topic when I first got started with JavaScript. My hope for this article is for you to be able to appreciate the beauty of JavaScript functions and closures.

The story of Lexical scope, scope chain and closures…

Once upon a time, there was a huge kingdom. This kingdom was called the land of prohibited control. Ps. I know the name is kinda off but bear with me. Alright, so this land had some laws. They are as follows -

  1. There are many sub-communities among the people. All the sub-communities may or may not have more sub-communities.

  2. The head of the kingdom is King Global*.* If there are any conflicts amongst the inner communities and they are not resolved by respective ministers, they come to the king for resolution.

  3. King Global shall punish anyone who’s a stealer or a pretender or a vanisher. (P.s. vanisher is not a real word. I invented it LOL. It basically means that a minister has mistakenly bought someone who isn’t there. Since the ministers have low eyesight.)

One day a brand new community was formed under King Global’s permission and it was called La La Land. It promised the exotics of the world. It allegedly had flying horsecars and dancing unicorns.

The ministers of La La Land decided that we must restrict the people to come in here. We must follow a procedure to let people in. So that people don’t pollute our land or steal our secrets or worst replace our secrets with duds. King Global agreed. The ministers of La La Land started deciding the rules for anyone to enter their community. They came up with the below plan:

  1. La La Land can have sub-communities. After all, they needed magicians to keep the people entertained and witches to protect the land from invaders. They needed elves to build their community even further. The ministers decided these were the roles that they would first let in their land.

  2. For the community secrets to stay secret they needed all these magicians, witches, elves and the ministers themselves to sign a magical treaty. The treaty restricted them to go outside of their sub-community to puke information.

  3. The ministers decided they shall have special helpers. There was a condition for the helpers. They can only send out messages, not people. So once you signed the magical treaty and entered La La Land, there was no way out. If you are a member of the sub-sub-community of La La Land then you will not be allowed in the outer community.

  4. But yes, the outer members can visit La La Land. And for people to believe that the land was honestly exquisite, they were allowed as tourists. Moreover, they can even have friendships with the community members.

The people of the kingdom were thrilled. The ministers of La La Land decided they would open the gates of this land on 30th Feb, Moonday.


Fast-forward to Moonday

You might be wondering right now, ‘I thought this was a JavaScript concept. Did I come to the wrong place’? Or ‘Okay stop already! How does this story relate to scopes or anything in JavaScript?’

Calm down folks, you’re at the right place. The story that you just read indeed had all the things you need to know about lexical scope and closures. Don’t believe me?

Let’s get out of the fantasy world and dive into the real one. These references will help you remember closures and lexical scope better.

  • The communities and sub-communities are functions and nested functions respectively.

  • La La Land is a community hence we consider it a function.

  • The magical treaty is lexical scope.

  • King Global is our global scope.

  • Tourists are the identifiers that are declared in outer scopes (hint: these tourists are closures).

  • Magicians, witches, and elves are nested functions of La La Land that forms closures with tourist.

  • Special helpers are the function references that are returned from inner functions.


Lexical Scope

Lexical scope means the area wherein a variable or a function is declared. Where the variable or function is declared makes it accessible or non-accessible in certain parts of the program.

It does not matter where you invoke the function, the variables inside the lexical scope remain intact. In some languages, lexical scope is also referred to as static scope.

The word lexical refers to the fact that lexical scoping uses the location where a variable is declared within the source code to determine where that variable is available. Nested functions have access to variables declared in their outer scope. — MDN Docs

Remember the communities and sub-communities of our kingdom? Yep, that’s lexical scope. Be it global, local or block. Let’s explore with an example.

function LaLaLand(){
    let magician = 'Harry Houdini';
    function introduce(){
        console.log(`Pleasure to meet you. I'm ${magician}`);
    }
    introduce();
}

LaLaLand()

In the above example, LaLaLand() is invoked in the Global Scope (territory of King Global). LaLaLand() has an inner nested function called introduce(). Since there is no magician in introduce(), the JavaScript engine decides to look in the scope that is just above itself. This looking for a variable in the outer scope(parent) is what forms a scope chain.

When a function (declaration or expression) is defined, a new scope is created. The positioning of scopes nested inside one another creates a natural scope hierarchy throughout the program, called the scope chain. The scope chain controls variable access, directionally oriented upward and outward. - You Don’t Know JS Yet: Scope & Closures

What happens if we did not have a variable called magician?

function LaLaLand(){
    function introduce(){
        console.log(`Pleasure to meet you. I'm ${magician}`);
    }
    introduce();
}
LaLaLand();
//ReferenceError: magician is not defined

💫 In our kingdom, disputes were sent to King Global if the resolution was not done within the sub-community. King looked for a magician in the above example but did not find any (We have a vanisher amongst us!)

  • Similarly, as per the rules of lexical scope, the inner function keeps on stepping up until we find a function / variable declaration or JS throws an error. For our example, we did not find any variable declaration and so there is a ReferenceError: magician is not defined.

  • Global scope is always the last resort for any scope chain.

  • We get a similar kind of error even when we are trying to call a function that does not exist.

💫 For tourists, we said that they can visit inside La La Land but the residents who signed the treaty were not allowed to go outside. How does that relate to lexical scope?

  • It means an inner scope has access to all the parent scope functions and variables (tourists). But the variables that are trapped within an inner scope (residents) are non-existent for outer scopes.

Now I hope with our analogy of La La Land, the lexical scope is clear.

Moving on to Closures ☠️


Closures

Let’s take reference from MDN Docs.

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.* - MDN

This definition is clear but we don’t fully grasp coding concepts by memorizing definitions. Coding time!

function LaLaLand(){
    let welcomeStatement;
    function greetMagician(name){
        welcomeStatement = `Welcome ${name}. You have signed the magical treaty and are a part of La La Land.`;
            console.log(welcomeStatement);
    }
    return greetMagician;
}

debugger;
let magician = LaLaLand();
magician('Harry Houdini');
//Welcome Harry Houdini. You have signed the magical treaty and are a part of La La Land.

LaLaLand()('David Copperfield');
//Welcome David Copperfield. You have signed the magical treaty and are a part of La La Land.

let oneMoreMagician = magician('Dynamo');
//Welcome Dynamo. You have signed the magical treaty and are a part of La La Land.

There are only 2 things you need to remember about closures. If you can gulp it down there’s nothing more to it.

They are - A closure is formed when,

  • A nested function makes a reference to a variable outside of its scope.

  • If we have a nested function but there’s no reference to a variable outside of its scope then it does not form a closure.

Let’s destructor the example.

💡
JS treats functions as First-Class Values. What that means is that function references can be stored in a variable and then passed around just like other variables.

We defined LaLaLand() and then call it in three different ways.

In the first one, LaLaLand() is invoked normally. The function returns a reference to another function.

It is not necessary that the name we declared in the nested function and the identifier where we store the reference have to be the same. So by invoking magician('Harry Houdini'); we successfully made our first closure.

This method is generally used while creating factory functions.

Guess which variable is closed over? It’s welcomeStatement.

Why? Because as we’ve seen in the definitions, welcomeStatement satisfies both conditions. It is outside of the nested block and it is referenced by inner block.

The interesting behaviour here is, even after the execution of LaLaLand() is finished, the inner function holds the variable welcomeStatement.

Closure is observed when a function uses variable(s) from outer scope(s) even while running in a scope where those variable(s) wouldn’t be accessible. - YDKJS

In the second invocation (LaLaLand()('David Copperfield');), instead of storing the method reference in a variable and then calling it, we directly invoked another function.

This syntax is a little tricky for beginners. This is called currying.

💡
Without parentheses, the reference of the function is returned or passed around.

The third instance is where we see different lexical environments. We had already stored a reference of the inner function in a variable. We used that reference to create another magician.

The first and third instances have different name parameters and JS ‘remembers’ them. What happened here is, our friendly JS engine created another lexical environment and preserved it in memory.

We could build an endless amount of magicians and this is the exciting part of factory functions. They sort of provide us with a blueprint for a class or an object. Before ES6, we did not have any clearly defined OOP concepts in JS. One of the methods used was the factory functions.

Closure describes the magic of keeping alive a function instance, along with its whole scope environment and chain, for as long as there’s at least one reference to that function instance floating around in any other part of the program. - YDKJS

Viewing closures in the browser console

If you wish to examine the function and where closure was created, fire up the console and paste it into the code. Since we have a debugger in our function it’ll pause the function flow.

On the right side, you’ll see a debugger window. Toggle on the Scope > Script > magician > [[Scopes]] > Closure.

You’ll be able to observe the entire behaviour of the function.

Don’t use closures accidentally

  • Since closures create a new instance and store it every time, it consumes a lot of memory. It is advisable to be aware of where your program uses closures. Common lookouts are even triggers, callbacks and loops

  • The closure will not be collected by the garbage collector until the function instance is stored and kept alive. This will affect the performance of your program.


Concluding

Keep the kingdom analogy in mind whenever you’re confused about closures and lexical scope.

In this article, I tried a newer approach towards making programming concepts easier by relating them to stories. Let me know if you like this way of understanding concepts.

Lastly,

  • The lexical scope mechanism allows the inner function to use outer function variables. This forms a closure.

Further reading

Did you find this article valuable?

Support Mariya Zaveri by becoming a sponsor. Any amount is appreciated!