Understanding Scope and Closure in Javascript

Understanding Scope and Closure in Javascript

Introduction

Scope determines the visibility of variable, functions and objects in your JavaScript code. If you forgot to declare some object and try to access it’s property, it is with the help of Scope that JavaScript knows that what you are trying to do is illegal and throws an exception. Understanding Scope is extremely important in creating error free code. Also they are building blocks in understanding advanced concepts such as Closure.

This is pretty long post with lot of concepts to absorb. I strongly encourage you to try the code samples simply in a browser console or using NodeJS.

Engine, Compiler and Scope

Most of us think that JavaScript is dynamic or interpreted language and not compiled like Java. It is wrong. JavaScript is “compiled”, but not well in advance like Java. In JavaScript there is no such phase because the compilation happens just before a piece of code is executed. Say you type var a = 10 in a browser’s console and press enter,then this code is compiled and instantly executed. There are 3 things that help run a program that we create in JavaScript,

  1. Engine - This is responsible for compilation as well as the execution of a JavaScript program.
  2. Compiler - A helper to engine. Generates the code that Engine executes later.
  3. Scope - Another helper of engine. This maintains a list of all the declared variables and enforces rules as to how they can be accessed by the currently executing code. Using this helper, Engine learns whether a variable is accessible in a function or not.

What happens when you type var a = 10 and press enter in your browser’s console

The statement var a = 10 may look like a single statement, but it is not. Engine is responsible for executing this statement. It sees this in two parts, one part it delegates to the Compiler and another to the Scope. There are lot of steps that I am leaving for brevity here, but the overview of what happens is given below.

  1. When encountering var a Compiler will ask Scope if there is a variable named a is already available. If available the Compiler moves on, otherwise it will request the Scope to create that variable. Finally it produces the code required for executing a=10 and hands it over to Engine.
  2. Now Engine will see the variable a and will ask the Scope if it is available. If the Scope is not able to find the variable requested by Engine, then the Engine will throw an error, if it finds then the value 10 is assigned to that variable. In our case, it does have that variable, and hence notifies the Engine. Engine proceeds with the remaining step.

Engine and Scope - a back and forth

In an operation such as number = 100 the variable number is Left-Hand-Side of the assignment operation and 100 is the Right-Hand-Side of the assignment operation. I am using this convention for name sake. Because what I mean by RHS is “find the value of the expression” and LHS by “find the variable in memory so that I can put some value inside of it”. In the example above number is the LHS, 100 is the RHS. For the number variable Engine will try to find its memory location so that it can put the value 100 in it.

var number = 100;
var factor = 2;
var result = factor * number;

For the LHS variables number and factor, Engine will assign the values 100 and 2 respectively. But for the result result it has to look up the values of factor and number. Engine does it after checking for their availability with the Scope. Remember Scope is the tool that keeps track of all the variables.

  • If you look in the memory to put some value then Engine performs LHS. If you give something to a variable then it is LHS.
  • If you request some value from the memory to use, then it is RHS. If you take something from the memory then it is RHS.

Let us look at another example below

function foo(a){    // line 1
    console.log(a); // line 2
}                   // line 3
foo(100);           // line 4
  • The function call foo(100) is an RHS reference. Here we are requesting for foo so that we can use it. That means RHS.
  • Note the parameter a in line 1. It is actually LHS. After the function call foo(100), the line 1 is replaced as foo(a=100). Here the value 100 is assigned to a (i.e.) we are giving something. That is LHS.
  • In line 2 console.log(a), the a is RHS, since we are looking up the value of a here. We are taking some value from a variable. Even though there is no assignment operation, we are still trying to find the values of the variable a and pass it along to the log function of the built-in console object.
  • When the code above is run, Compiler generates the code and requests the Scope to create a function named foo and variable named a inside foo. Once this code is generated and variables are declared, the control is handed over to the Engine for the execution of the code and Engine dutifully performs it. Here is how conversation between Engine and Scope goes when Engine starts the execution. It starts at line 4 when the foo is called with the value 100.

Engine Scope there is a RHS reference for a function named foo, do you have it ?

Scope Yup. I do. By the way, Compiler asked me to declare it momenets ago. Here, take the function.

Engine Awesome, executing it and passing the function with the value 100. There is a LHS reference for a on foo. You have it ?

Scope Yeah. It is declared as a parameter of foo just now.

Engine Thanks. Now let me assign 100 to it.

Engine Now there is a variable named console. Do you have it?

Scope Ofcourse, it is a built-in object. Here you go.

Engine Thanks. Looking up for a function named log inside console.

Engine There is a RHS reference for a. Can you help me out ?

Scope Definitely. He is the same a that was declared as the parameter to foo.

Engine Nice. Passing the value of a which is 100 to log(...)

Nested scope

What we saw so far is looking up variables inside a single scope. In the example we were looking for the variable a inside foo. A function or a block can be nested inside another function or a block. In the same way, scope can be inside another. For every functional block, there is a separate Scope. This is very importance to remember. So if a variable is not found in the current scope, it is searched in outer scope, continuing until it is found or the outer most scope(the global scope) is reached. Check this example

function add2(a) {       // line 1
    console.log( a + b );// line 2
}                        // line 3
var b = 20;              // line 4
add2(10);                // line 5

Let us see how the conversation between Engine and Scope goes. Since the execution starts outside of add2, let us assume we are in the global function. That means Engine will start requesting from the global scope.

Engine Hey global Scope, have you created a variable named b?

Global Scope Yes.

Engine Excellent. Then let me assign the value 20 to it

Engine Is there a function named add2 in your list ?

Global Scope Yes. Here you go.

Engine Excellent, let me call the function and pass the value 20 to it. So, I am inside add2 now. Here after I should stop checking with the global Scope for variables. Hey Scope inside add2 function, do you know anything about a ?

add2 Scope Yes, it is assigned as a parameter to the function add2

Engine Great. Let me assign the value 10 to it. Now what about the console?

add2 Scope I do not know what that is.

Engine Oh ok. Then I should check with the Scope outside of add2 function’s Scope. Which in this case is the Global Scope. Hey Global Scope, do you know anything about console ?

Global Scope Yes. I have it with me. Take it.

Engine Thanks you. Now back to add2 Scope. Do you know anything about the variable a?

add2 Scope Yes. It is available with me.

Engine Great. Do you have b too ?

add2 Scope Nope. I don’t

Engine Oh ok. Then I should check with the Scope outside of add2 function’s Scope. Which in this case is the Global Scope. Hey Global Scope, do you know anything about the variable b ?

Global Scope Yes. I have it with me.

Engine Awesome. Now I have everything. Let me add a and b and send the result to the log function of the console object.

Another example,

var b= 10;
function mathFunction(a){
    function sum(){
        console.log(a + b); // 20
    }
    function product(){
        console.log(a * b); // 200
    }
    sum();
    product();
}
mathFunction(10);
  • The code above starts with the function call mathFunction(10).
  • When this function is executed by Engine, it checks for the existence of the functions sum and product in the mathFunction’s Scope.
  • Since those functions are available inside, the Engine executes sum.
  • Inside sum function it encounters a and b. And checks the sum function’s Scope for those variables. Now, the a is not available inside sum. So it checks the outer scope which is mathFunction’s Scope. Here the Engine finds a. It does the same for b. It is not found in sum, so it checks inside mathFunction. b is not available even there. So it checks the Scope outside mathFunction, which is the global Scope. Finally Engine finds that b is available in the global Scope, outside of mathFunction, and makes use of it. This process is repeated for the product function too.

Errors

  • We have been using the terms LHS for finding the memory location of a variable in the Scope and RHS for resolving a variable’s value. There is a difference between LHS and RHS in how the variables are handled.
  • If RHS look up fails to find a variable(anywhere between local to global scope) ReferenceError is thrown by the Engine. Remember, we are trying to receive some value from a variable. If the variable itself is not available, there is no way we can receive it’s value right ? That’s why we are getting the ReferenceError.

    // global scope
    var a = 10;
    function foo(){
    // local scope of foo
    // The Engine tries to resolve a and b. a is resolved successfully, 
    // b is not
    // We will get "Uncaught ReferenceError: b is not defined"
    console.log(a*b); 
    }
    
  • In contrast, if LHS look up of a variable fails (i.e.) no variable of the same name from the local Scope till the global Scope, a new variable is created by the global Scope(except in the Strict mode). Remember, we are requesting a memory location or a container so that we can put some value inside of it. The global Scope seeing that there is no container(variable) available, will create one and give it to the Engine.

  • In the example below, Engine will request the local Scope for the variable b. It won’t be available. Then it will go to the Global Scope. The Global Scope seeing that there is no variable named b and also noting that it is a LHS operation, will create a new variable named b in there and hand it over to the Engine.

    // global scope
    var a = 10;
    function foo(){
    // local scope of foo
    // A variable named b will be created in the Global scope
    // the value 20 will be assigned to it
    b = 20;
    console.log(a*b); 
    }
    
  • ReferenceError means Scope resolution has failed, whereas TypeError implies that Scope resolution was successful, but that there was an illegal action attempted against the result like trying to assign one type of variable to another type.

Functional scope

JavaScript creates Scope inside every function. From ES6 block Scope is introduced. We will see about that later.

Where ever a declaration is made inside a function, those variables are considered to be part of the function’s scope. Each function that is declared, creates a scope for itself. So far whatever example we have seen they are all examples of Scope created inside functions.

// Global scope
function getTitle(gender){
    var title;
    // getTitle scope/local scope
    if(gender==="MALE")
        title = "Mr.";
    if(gender==="FEMALE")
        title = "Mrs.";
    return title;
}
function formatName(name,gender){
    // formatName scope/local scope
    return getTitle(gender)+name;
}
// Global scope
var name = "Einstein";
var gender = "MALE";
var fullName = formatName(name, gender);
console.log(fullName); // Mr.Einstein
console.log(formatName("Curie", "FEMALE"));//Mrs.Curie

Just like how putting variables inside a function creates Scope and adds those variables to that Scope,we can do the reverse and cover a block of code with a function and create a Scope.

In the example above, say if we do not want to expose the function getTitle in the global Scope, we could simply cover the entire code with a function. In this case, I have taken the function getTitle out of the global Scope and have put it inside the formatName function.

Why would we want to do that ? Because we should expose only what is necessary and hide everything else. This is a software design principle called Principle of Least Privilege. What this principle states is that every program should access only the information and resources that are necessary for it. Here, JavaScript gives us the ability to hide variables, functions that are not needed by certain parts of the code.

// Global scope
function formatName(name, gender){
    // formatName scope
    function getTitle(gender){
        var title;
        // getTitle scope
        if(gender==="MALE")
            title = "Mr.";
        if(gender==="FEMALE")
            title = "Mrs.";
        return title;
    }
    return getTitle(gender)+name;
}
var name = "Einstein";
var gender = "MALE";
var fullName = formatName(name, gender);
console.log(fullName);                      // Mr.Einstein
console.log(formatName("Curie", "FEMALE")); // Mrs.Curie

One more important aspect of hiding variables inside another Scope is collision avoidance. If two identifiers that are available in a Scope have the same name, then the identifier which is resolved first, is used.

// Global scope
var number = 10;
function getNumber(){
    // getNumber's scope, local scope
    var number = 5;
    // Since the "number" is resolved with in the local scope itself, 
    // the value that is returned will be 5 always
    return number;
}
console.log(number);      // 10
console.log(getNumber()); // 5

Block scope

let

ES6 has introduced a new keyword named let. Using let is an another way to declare a variable. When let is used inside a block ({...} pair), it creates a Scope inside of that block for the variable it declares.

function foo(){
    let number = 10;
    ...
}
console.log(number);// ReferenceError

Using let you can create a temporary variable in side of a for loop variable

for(let i=0;i<10;i++){
    // A new scope is created here for i
    console.log(i);//0 1 2....
}
console.log(i);//ReferenceError: i is not defined

const

Another introduction in ES6 is const variable. This works exactly like let except, during the declaration a value must be assigned. It’s value is fixed. Any attempt to change the value results in error.

if(true){
    const a = 10;
    console.log(a);//10
    a = 20;// TypeError: Assignment to a constant variable
}

Hoisting

What do you think would be the output of the following code snippet ?

Snippet 1

number = 100;           // line 1
var number;             // line 2
console.log(number);    // line 3

Many would assume that it would be undefined. Because we think that Engine executes the code line by line. In that logic, once could say that when the Engine encounters the variable number at the 1st line, it would check the current Scope for its availability and since it is not available yet, it will keep on requesting all the enclosed Scope until the global Scope takes pity, creates the variable and returns it.

Then when line 2 is executed, using the same logic that we applied in the previous paragraph, we would think that a new variable named number is declared in the current Scope and since a variable in the current Scope has higher precedence than the one in the outer Scope, we will get undefined on line 3.

Sounds logical right ? Except it is not. The output is 100.

Take a look at another piece of code,

Snippet 2

console.log(number); // line 1
var number = 10;    //  line 2

What would be output ? Againg 100? Nope, this time it is undefined indeed. What is happening here ? Recall that we saw that before executing a piece of code Engine requests the Compiler to compile the code. What does a Compiler do ? It asks the Scope to create variables that are undeclared yet. The thing is, before any of the code is executed, all the variables and functions are processed. When we see var number = 10, we see that as a single statement. But for JavaScript(for Compiler to be precise) it is two statements. The 1st statement is the declaration var number is processed at the compilation phase and the next statement number = 10 is left in the same place to be processed during the execution.

The Snippet 1 is processed like this,

During compilation,

var number;            // line 2

during execution,

number = 100;          // line 1
console.log(number);   // line 3

Similarly, the Snippet 2 is processed like this During compilation,

var number;          // line 2

During execution

console.log(number); // line 1
number = 10;         // line 2

It seems like declarations are processed separately or moved away to the top of the Scope. That is what happens. This is called Hoisting. Declaration comes before assignment always. To be exact, declaration is moved to the top and the assignment stays at the same place. One thing to remember is that this Hoisting process happens for every Scope. Global Scope, local Scope, I mean for every Scope. Not just variable declarations, but function declarations are also hoisted.

var num1 = 10;
var num2 = 20;
sum(num1, num2);
function sum(a,b){
    console.log(a+b);
}

Can you guess how the code snippet above is processed ? During compilation

var num1;
var num2;
function sum(a,b){
    console.log(a+b);
}

During execution

num1 = 10;
num2 = 20;
sum(num1,num2);

Here the function sum is hoisted to the top of the Scope just below the variables num1 and num2. That is why, even though we are calling the function sum before it is even defined, we get the correct result.

Speaking of functions, I told that function declarations are also hoisted. But function expressions are not hoisted. Look at the code below.

sum();                     // line 1
var sum = function add(){  // line 2
    console.log("In sum"); // line 3
}

Here, this is modified as in the compile time

var sum;

In the execution time,

// A variable named sum is declared, but no value is assigned yet
sum();
// Now a function reference is assigned to the variable sum
sum = function add(){ 
    console.log("In sum");
}

If we run the code we get TypeError: sum is not a function. The var sum = function sum... is a function expression. It is split into two parts, one is declaration and another is assignment. The declaration is hoisted. But the assignment is not. As expected.

The reason we get TypeError is that the sum is undefined in line 1 even though it is declared. We are trying to use an undefined as a function reference. This is illegal operation. If a variable is not available in the Scope and if we try to request it’s value, then we get ReferenceError. But if the variable is available and if we try to perform an illegal operation such as calling a variable as if it is a function, then we would get the TypeError.

Remember, all the declarations are hoisted to the top of the Scope (i.e.) to the top of the function where they are declared. Ofcourse, this does not apply to the declarations done using the let or const keywords. They stay exactly where they are declared.

Closure

We have so far seen about the Scope inside functions, the Scope inside blocks or even the global Scope. There is something that combines all these scopes. It is called Lexical Scope. Take a look this code snippet. By understanding this, we will be able to explore more about the Lexical Scope.

// global scope
let n1 = 10;
function numbers(){
    // scope of the numbers function
    var n2 = 20;
    function product(){
        // scope of the product function
        return n1 * n2;
    }
    console.log(product());// 200
}
  • The variable n1 declared in the global Scope is available in the global Scope and in the Scope of numbers and product functions.

  • The variable n2 declared in the Scope of numbers function is available in the numbers function as well as in the nested function product. The product is a function nested inside the numbers function.

  • The Lexical Scope of a piece of code is all the Scopes that are available to it. For the product function the lexical Scope includes the global Scope, the numbers function’s Scope and its own local Scope. That means all the variables, function, references that are available in those scopes, also available in the product function’s Scope.

Now let us take this up a notch and take a look at this example. Check what would happen when the function process is executed.

function formatter(){
    let greeting = "Hello";
    function format(name){
        return greeting+" "+name;
    }
    return format;
}
// .......
// Some where in the application
// .......
function process(){
    var formatFunction = formatter();
    formatFunction("JavaScript");// Hello JavaScript 
}
process();
  • The formatter function returns another function named format. Remember, we can store functions in variables and pass them around. In this case, when calling the formatter, it returnes the format function.
  • Now what this format does ? It simply joins the variable greeting with the variable name. Pay attention to the greeting variable. This variable is available inside the Scope of the formatter function. By the definition of the Lexical Scope, this variable is also available to all the nested Scope of the formatter function. That means only one, format. The format function has access to its local Scope, the enclosing Scope (i.e.) formatter’s Scope and finally global Scope.
  • This Lexical Scope of the format function always stays with it, where ever it goes. One can pass around the format function anywhere in the application. It will still have access to the greeting variable. Why is this such a big deal ? Because we might think that once the formatter function completes its execution, all the variables and functions declared inside might be garbage collected(simply removed from the memory). That is the case usually. But it does not happen now. Because the Scope inside the formatter is still being used by the format. This reference that format has over the Scope of formatter or any other enclosing scopes, is called Closure.
  • Closure is created when an inner function is somehow made available to the Scope outside the outer function. In the example above, the inner function is format and the outer function is formatter.

To drive home my point, let me present another example too. In this example, whenever you click a button with id “btnId”, an alert with the click count will be displayed. Here you are passing the clickCounter function to the addEventListener, a built-in function.

// Global scope
var counter = 0;
function clickCounter(){
    ++counter;
    let message = "Your click count: "+counter;
    alert(message);
}
const button = document.getElementById("btnId");
button.addEventListener("click",clickCounter);

The problem with this code is that the counter variable is available in the global Scope. This means that it is available through out the application. It is an extremly bad thing now, because any piece of code can change or update this variable. A for loop created with the same variable name in the application. This will override the counter variable’s value in the global Scope.

// Global scope
for(var counter = 0; counter<10;counter++){
    console.log(counter);
}

Some where in the same application, one might have created a variable with the same name (i.e.) counter. This is the kind of thing that introduces unpredictable behaviour and bugs. All this has happened because the counter variable is not private and is available to the code which does not need it.

The closure comes to the rescue here. Check out the program below. The counter variable is not accessible outside the clickCounter. But it still gets updated whenever the button is clicked. It is because the alertMessage function has closure over the Scope of the clickCounter.

function clickCounter(){
    let counter = 0;
    return function alertMessage(){
        ++counter;
        let message = "Your click count: "+counter;
        alert(message);
    }
}
const button = document.getElementById("btnId");
button.addEventListener("click",clickCounter);

Closure provides data hiding here. We are not exposing the variable counter to the outside world, but still it is doing the counting perfectly. Closure introduces the concept of private variables to the JavaScript.

Let us see another example. Now we want access to a variable. But should not be able to change it. How to implement that ? Take a look at the keyProvider function. keyProvider returns a function, which gives the secretKey. This secretKey can be viewed, but can not be changed.

function keyProvider(){
    const secretKey = "abc";
    return function (){
        return secretKey;
    }
}
var getKey = keyProvider();
var key = getKey();
console.log(key);//abc

With this I conclude my summary of Scope and Closure in JavaScript.

To know more about Scope check out this question in the stackoverflow. To know more about closure checkout this question in stackoverflow.