JavaScript Scope Fundamentals with Tom and Jerry

JavaScript Scope Fundamentals with Tom and Jerry

Introduction

Welcome to another post of the series, JavaScript: Cracking the Nuts. This series is all about visiting JavaScript fundamental concepts with more significant details. In this article, I am going to explain another important concept called Scope. We will also learn and appreciate the importance of the Scope Chain.

If you are new to the series and want to check out the previous articles, here are the links,

Thank you for showing all the love to the series so far, I really appreciate your feedback, likes, and constructive criticisms. I hope you find this one useful as well.

Quiz Time

What will be the output of the following code execution and why?

function jerry() {
  console.log(name);
}

function tom() {
  var name = 'tom';
  jerry();
}

var name = 'cartoon';

tom();

Is it going to be cartoon, tom or undefined? But more importantly, how are you deciding on an answer here? Are you going by the Scope? What about the execution context?

Scope

The answer to the question I have asked above is, cartoon. Let us explore and understand it further.

In JavaScript, Scope is the mechanism to determine where the variables exist to use. The variable may exist inside or outside of a function call.

Let us break the above code into pieces and see how the variable's accessibility changes depending on where the variable has been declared, and the functions are created.

Recap

Here are some of the key points from our Understanding of JavaScript Execution Context:

  • There is something called Global Execution Context and Function Execution Context.
  • Each execution context has a special thing called this and the reference to the Outer Environment.
  • When we invoke a function, the JavaScript engine creates an outer reference for the current Function Execution Context.
  • The function has access to the variables defined in the Outer reference. The JavaScript engine does a look-up when it is unable to find it in the current execution context.

Scope and Scope chain

In the example above, there are two function invocations, tom() and jerry(). Hence there will be two different function execution contexts created.

Remember, there is always a global execution context created where the keyword this is equal to the Window object. Hence we have a total of three execution contexts here, one Global Execution Context and two function Execution Contexts of tom() and jerry() respectively.

functions.png

  • The variable name was created in the global execution context and assigned a value as cartoon in the execution phase.
    var name = 'cartoon';
    
  • When the function tom() was invoked, the JavaScript engine created an execution context for tom() and a reference to the outer environment, the global execution context.
    tom();
    
  • When tom() invokes jerry(), JavaScript engine identifies the lexical position of jerry() and does the same. It creates an execution context of jerry() and a reference to the outer environment.
    function tom() {
     var name = 'tom';
     jerry();
    }
    

Hold on. What's the outer environment of jerry()? Is it the execution context of tom() or the global execution context? This depends on the answer to another question.

Who created jerry()? Where is it sitting lexically?

jerry() is created by the global execution context even though it was invoked in tom()'s execution context. We find that jerry() sitting lexically at the global execution context and created by it. As we go by this theory, jerry() is having a pointer to the global execution context.

So far, so good? We also find, jerry() doesn't have a variable declared called name in it. In the execution phase, it tries to log the name variable.

function jerry() {
  console.log(name);
}

In the execution phase, the JavaScript engine starts the look-up process following the outer reference of jerry() and finds a variable name created with value, cartoon in the global execution context.

Now we know why the answer to the question has to be cartoon, not tom or undefined. Here is the visual flow of how the scoping took place,

flow_1.gif

The whole process of looking up for the variable in the current execution context and outer references form a chain called the Scope Chain. We can also conclude that the variable name is in the scope of the function jerry() because it was successfully found in its scope chain.

scope_chain.png

Change in the Chain

Quiz time again! What will be the output of this code execution?

function tom() {
  var name = 'tom';
  function jerry() {
    console.log(name);
  }
  jerry();
}

var name = 'cartoon';

tom();

We have made a small change in the above code. Now the function jerry() is created inside tom(). The reference to the outer environment from jerry()'s execution context will be pointing to tom()'s execution context. Hence the variable name will be found in the scope chain as defined in the tom() function. So you know the answer is, tom!

flow_2.gif

Block Scope

As we got the fundamentals of scope, let us understand what block scope is. A code block is defined by these braces {...}. If a variable is declared within a code block using a keyword called let, it’s only visible inside that block.

{
  let name = "tom"; // only visible in this block

  console.log(name); // tom
}

console.log(name); // Error: name is not defined

Had we created the variable name with var instead of let, we wouldn't have found this block scope restriction. Here is another example,

{
  // declare name
  let name= "tom";
  console.log(name);
}

{
  // declare name in another block
  let name = "jerry";
  console.log(name);
}

This is going to work perfectly fine and logs tom and jerry in the console.

Even for if, for, while etc, variables declared inside the block({...}) are only visible inside it. Here is an example with for loop,

for (let counter = 0; counter < 10; counter++) {
  // the variable counter is with let 
  // hence visible only inside the block {...}
  console.log(counter); 
}

console.log(counter); // Error, counter is not defined

Conclusion

Understanding scope with the fundamental concepts like execution context, outer reference, lexical positioning, etc., will help debug the tricky bugs(those horrible production ones) with ease. We, as JavaScript developers, will be more confident about how things work internally.

Here are a few references I liked and followed on this subject,


I hope you find the article useful. Please Like/Share so that it reaches others as well. If you enjoyed this article or found it helpful, let's connect. You can find me on Twitter(@tapasadhikary) sharing thoughts, tips, and code practices.

To get e-mail notifications on my latest posts, please subscribe to my blog by hitting the Subscribe button at the top of the page.

Up next in the last post of the series, I'll be explaining another fundamental concept called, Closure. Stay Tuned.

Did you find this article valuable?

Support Tapas Adhikary by becoming a sponsor. Any amount is appreciated!