JavaScript Scope Fundamentals with Tom and Jerry

Subscribe to my newsletter and never miss my upcoming articles

Introduction

Welcome to another post of the series, JavaScript: Cracking the Nuts. This series is all about visiting JavaScript fundamental concepts with greater details. In this article, I am going to explain another important concept called, Scope. We will also learn and appreciate the importance of 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, really appreciate your feedback, likes and constructive criticisms. 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 of the question I have asked above is, cartoon. Let us explore and understand it further.

In JavaScript, Scope is the mechanism to determine the accessibility of variables throughout its existence. It could be 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 and 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 total 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, JavaScript engine created an execution context for tom() and a reference to the outer environment which is 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 of 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? What we also find is, jerry() doesn't have a variable declared called, name in it. In the execution phase, it tries to to log the name variable.

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

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

Now we know, why the answer of 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 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 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 in debugging the complex bugs(those horrible production ones) with ease. We as JavaScript developers will be more confident about how things work internally.

Here are few references I liked and follow on this subject,


If it was useful to you, please Like/Share so that, it reaches others as well. To get e-mail notification 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 about another fundamental concept called, Closure. Stay Tuned.

Comments (2)

Victoria Lo's photo

I love reading your entire Cracking the Nuts series! It's always so insightful and well-explained! Thanks!

Tapas Adhikary's photo

☝ UI/UX Enthusiast | πŸ’» Work @MicroFocus | ✍️ Blogger | πŸ‘¨β€πŸ« Mentor | 🎀 Speaker | 🍟 Foodie

This made my day!