A deep dive into scope in JavaScript

As the name suggests, the scope basically defines where the boundaries and rules for a variable in JavaScript like where it will be stored, how it will be accessible, what is its lifetime and who can access it. While the concept of scope itself is not complex, most of the times the beginners find it difficult to grasp and even the seasoned developers get confused. Hence, I thought of putting an article on this topic to help you understand this easily and point you to the relevant resources for deeper understanding.

Like any language, JavaScript also has two types of variables

  • Global Variable
  • Local Variable

In the simplest form, the global variables are outside the functions and local variables are inside the functions. Also, the variables in JavaScript follow lexical scoping (also known as static scoping), which basically means that at the compile time itself the scoping gets decided based on where the variable and block of scopes are written.

Unlike some of the languages like Java, the scope block in JavaScript is at the function level.

  • In Java you can define a new block using curly braces ({}), which doesn’t work in Javascript
  • Hence, it is always recommended to define all the variables at the top of the function definition

What is the big deal?

If there are two types of variables and the local variable scope is determined by the function scope then what is the big deal?

Well there is no big deal if you understand following as well

  • Function has parameters, which is also a kind of variable
  • Function can have nested functions and
  • Those functions can be in different shapes and formats (e.g. standard declaration, function as expression, anonymous function, self invoking function, etc).
  • Function itself can be considered as an object
  • Child function may need to access the property of the parent object (function)
  • You may like to hide the declarations, until it is really needed and clean up when its usage is done
  • And there could be various other possibilities and patterns

With all these things to think about, the scope does start looking overwhelming to the beginners. And, JavaScript appears like a difficult nut to crack.

Let’s make it simpler

Let’s look at the below diagram to see how the scope look-up works:

scope1

Above diagram basically depicts following code:

var a = "One";
function regularFunction(c) {
  var b = "Two";
  function foo(a) {
    var a = " Seven ";
    console.log(a + f + c);
  }
  foo(" Four ");
  function bar(b) {
    var f = " Eight ";
    console.log(a + b + c + f);
  }
  bar(" Five ");
}
regularFunction(" Six ");

Now, execute this code and observe the output:

It says : Uncaught ReferenceError: f is not defined

The variable f, inside the console statement of the foo function is not defined – either in the foo function or in the regularFunction or the global scope.

Now remove the reference to the variable f in the foo function:

scope2

When you execute above code, it gives following output:

Seven  Six 
One Five  Six  Eight

Let’s analyze this output.

  • The first line is coming from the console statement in the foo function
    • The value of the variable a is being picked from the local scope instead of the global scope or instead of even the arguments of the foo function
      • In the above code, if you don’t assign a value to the var a in the foo method (i.e. the code is like var a; // = ” Seven “;) then it will go ahead and print an output “Four Six”, which indicates that just declaring but not initializing or changing the value will not result into shadowing the current function’s parameter.
        • However, similar kind of code will indeed shadow variables declared in parent (or any ancestor) function’s scope or global scope
    • The value of c is coming from the parent function argument
  • The second line is coming from the console statement in bar function
    • The value of a comes from the global scope
    • The value of b from the argument
    • The value of c from the parent function argument
    • The value of f from the local scope

Hoisting

So far we focused on the variables and parameters. However, even the function definitions and its position relative to its usage impact the scope. Before we go there, let’s understand hoisting.

As part of hoisting behaviour, JavaScript moves all the declarations to the top of the scope. What it means is that

  • In case a declaration is inside a function, it gets moved to the top of that function
  • In case a declaration is in the global scope, it gets moved to the top of the script

Thus a variable can be used even before its declaration. This sound awkward and you must avoid this.

Let’s understand this concept using following example:

var a = " One ";
function regularFunction(c) {

  // 1. invoking function and using variable before declaration/definition
  bar(bDeferredInit);
  bPlus = " Ten ";

// 2. using the variable, which is being declared later
  foo(bPlus);     
  function foo(a) {
    console.log(a + c);
  }

  function bar(f) {    
   console.log(a + b + c + f);
  }

// 3. initializing the variable at the time of declaration
  var bDeferredInit = " Two "; 

 // 4. comment this and observe the output in the global scope
  var bPlus;            
  var b = " Eleven "; 
  bar(bDeferredInit);
}
regularFunction(" Six ");
console.log(" Value of bPlus in Global Scope: " + bPlus);

 

When you execute this code, you get following output:

One undefined Six undefined
Ten  Six 
One  Eleven  Six  Two 
Uncaught ReferenceError: bPlus is not defined

What happened here?

Following is the relevant information that you shall notice while understanding this example code

  • Although bar function is invoked before its definition, the function is actually getting executed. That does show that functions get hoisted to the top of the including function.
    • However, at this stage the bDeferredInit and the variable b are undefined and that get printed on the console
  • In case of bDeferredInit, the initialization happens within the function at the later stage. Hence the first bar call, printed undefined as value, while the second bar function call prints the initialized value.
    • This happened because the variable initialization doesn’t get hoisted
  • The variable bPlus is initialized earlier, while declared later. Which is perfectly fine because variable declarations get hoisted to the top.
    • However, if you comment out the bPlus declarations towards the end, then the initialization will force the variable to become a global variable.
      • In strict mode, this may also result into an error “Uncaught ReferenceError: bPlus is not defined
  • Since the variable declaration for bPlus got hoisted, it doesn’t go in the global scope. Hence, there is a reference error when it gets used in the global scope.

Scope of a function

In above section, we learned how to resolve scope of a variable defined through var keyword. However, that is not all which adds to scope confusion. Let’s look at the different ways in which the functions are defined and used and how they impact the scoping.

Before you proceed, refer to our previous article on Javascript functions explained to stay with your forever. In this blog, I explained two ways of declaring functions

  • As standard definition
  • As expression / statement

Note that the browser takes the JavaScript code through two passes.

  1. First, it looks up for the standard function definitions and puts them right on the top of the parent (i.e. containing) function — ahead of any variables or functions expressions
  2. Next, it starts executing the expressions one after another

Note

Apart from above two ways of using functions, of course, you can use functions within an expressions and in such case the variable scope gets further limited to that expression only.

Other important things where scopes get impacted

Conditionally defined child functions

Let’s take a look at below code:

var a = " One ";
function regularFunction(c) {
 function foo(a) {
    console.log(a + c);
  }
  if (a === parseInt(a, 10)) {
    function bar(f) {    
      console.log(a + c + f);
    }
  }
  foo(" Five ");
  bar( " Eight " );
}
regularFunction(" Six ");

In above code the function bar is defined conditionally. Unfortunately, the global variable, a, is is not an integer and bar function remained undefined. Hence you get the following message when you invoke the bar method:

Uncaught ReferenceError: bar is not defined

Now, let’s change the the value of a from “One” to 1 and try again. This time you will see the desired output. Once the function becomes available, of course, it remains available.

The point you should be aware of is that the functions can be defined conditionally and in such cases sometimes it may not be available in your execution scope.

For loop

The var declaration inside the forloop also gets hoisted to the top. Run following code to understand the behaviour:

var a = 1;
function regularFunction(c) {
    function foo(a) {
    console.log(a + c);
  }
  if (a === parseInt(a, 10)) {
    function bar(f) {    
            console.log(a + c + f);
      console.log( "value of i before the for loop : " + i );
      for (var i = 0; i<5; i++) {
        console.log(i);
      }
      console.log( "value of i after the for loop : " + i );
    }
  }
  foo(" Five ");
  bar( " Eight " );
}
regularFunction(" Six ");

It gives following output, which means that the variable i is declared but it is not initialized before the for loop:

Five  Six 
1 Six  Eight 
16 value of i before the for loop : undefined
0
1
2
3
4
value of i after the for loop : 5

And, its value remain available after the for loop. In standard languages, the block level scoping behave differently and hence many developers find this confusing.

Try and Catch

Like any other language, the catch variables are local to the block. So, this is not confusing, but good to mention so that you know that the try catch block respect traditional scoping.

If you run this code, the console statement inside the catch block prints the error object, which the console object outside of catch block throws reference error.

function bar(a) {    
  try {
    throw {
      error : " Throwing error"
    };
  } catch(error) {
     console.log(error);
  }
  console.log(error);
}
bar(5);

 

Module and Module Reveal Design Pattern

The Module pattern is used to derive the concept of classes in such a way that we’re able to use public and private methods and variables inside an object. Let’s understand this by using the following example:

var fooBar = (function() {
    var c = " One ";
    function foo(a) {
      console.log( "Foo : " + a + c);
    }
    function privateBar(p) {
      return " privateBar : " + p;
    }
    function bar(b) {
      console.log( "Bar : " + privateBar(b));
    }
    return {
      // everything inside this object will have the public visibility 
      foo : foo,
      barFun : bar
    };
})();
fooBar.foo(20);
fooBar.barFun(22);
fooBar.privateBar(24);

When you run this code, it gives you following output:

Foo : 20 One 

Bar :  privateBar : 22

Uncaught TypeError: fooBar.privateBar is not a function

What did we do?

Here we have an self executing anonymous function, which essentially defined set of functions and returns an object consisting of functions and properties that it wants the outside world (the world beyond this specific function) to know. This mechanism provides an excellent way of controlling the visibility (private/public access) of a method or property.

  • The return object provides you the public visibility
  • Rest of the declaration inside the anonymous function remains private for the fooBar
  • Module and Module Reveal pattern differs in terms of approach. In case of Module Reveal pattern the functions and variables are defined in private scope and the object literal wraps the references and returns to the invoking scope.
    • In case of Module Design pattern the private functions and variables are kept in the private scope and the public declarations are in the object literals being returned.
    • I personally find Module Reveal Pattern to be much cleaner

Good Habits

While I have covered most of the common cases, where scope gets impacted, there are few good habits, which further improves the readability and it becomes easier to manage the variables.

Global Variables

  • Instead of keeping the global variables scattered all over the places in the project, it is better to put them in a single file
  • Also, grouping them under relevant objects can further enhance readability and improve overall global footprint

For example

var connectionParams = {
  url:”http://someurl.com/”,
  login: “username”,
  password : “secret” 
} 

You can access these global variables using the dot notation (e.g. connectionParams.login).

Bad coding practices

In this article, I have ignored the bad coding practices like

  • Using the same parameters name in the same function declaration
  • Using the same parameter name as the function name
  • Using evals
  • Usage of with statements, Etc..

In each of these cases the scoping gets impacted. I don’t want to talk about how, because I don’t want you to know or use this style of coding.

Read More

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s