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,
- JavaScript Interpreted or Compiled? The Debate is Over
- Understanding JavaScript Execution Context like never before
- JavaScript Hoisting Internals
- JavaScript: this is easy and what do you need to know about it!
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.
- The variable
name
was created in the global execution context and assigned a value ascartoon
in the execution phase.var name = 'cartoon';
- When the function
tom()
was invoked, the JavaScript engine created an execution context fortom()
and a reference to the outer environment, the global execution context.tom();
- When tom() invokes
jerry()
, JavaScript engine identifies thelexical
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,
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.
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
!
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,
- Scope and Closure from You don't know JS yet series.
- Variable Scope from javascript.info
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.