DEV Community

loading...
Cover image for What is the Execution Context, Execution Stack & Scope Chain in JS

What is the Execution Context, Execution Stack & Scope Chain in JS

Tahir Ahmed Tapadhar
Tech Enthusiast, C#, Javascript, .NET , Cloud, Life-long Learner, Polymath & an aspiring polyglot
Updated on ・6 min read

If you are or want to be a JavaScript developer, or are learning javascript, then you must know how the JavaScript programs are executed internally.Understanding of the execution context and execution stack is vital in order to understand how and why javascript works the way it does. Every javascript piece of code internally uses these concepts and knowing them well will surely make you a much better JavaScript developer.

Execution Context:

There is always a context present with javascript in which your code is being executed. Every block of code will have its own context in which it is executing. Simply put, an execution context is nothing but an environment where the Javascript code is evaluated and executed. Whenever any code is run in JavaScript, it’s run inside an execution context.

There are 3 different types of execution context in JavaScript:

  • Global Execution Context (GEC):
    This is the default or base execution context. The code that is not inside any function is in the global execution context. It performs two things: it creates a global object which is a window object (in the case of browsers) and sets the value of _this_ to equal to the global object. There can only be one global execution context in a program.

  • Function Execution Context (FEC):
    Every time a function is invoked, an execution context is created for that function. The execution context is destroyed when the associated function has returned or execution is over. Each function has its own execution context, but it’s created when the function is invoked or called.

  • Eval Execution Context (EEC):
    code inside an eval function also has its own execution context. But since it is now deprecated & also not recommended, we shall not discuss it.

Below is a sample code snippet demonstrating the execution context.

/* global execution context */

// every execution context has it's own variable environment
// GEC: global execution context
// FEC: function execution context

function baz(){
   // FEC of baz
   var foo = 3;
   console.log(foo); //3
}

function bar(){
   // FEC of bar
   var foo = 2;
   console.log(foo); //2
   baz();
}

var foo = 1; //GEC
console.log(foo); //1
bar();
console.log(foo); //1

/* global execution context */
  • When the program runs, the first thing the javascript engine creates is a global execution context. Everything that is not inside a function is a part of the GEC. So the var foo is in the GEC & is put into memory space.
  • When the javascript interpreter comes across bar(),the function is invoked, a new execution context is immediately created and everything inside of it starts executing.
  • Now this is the most important point to remember: 'Every execution context (FEC) has it's own variable environment'. Therefore when the variable foo is declared again with value 2, it is created only within bar()'s execution context.
  • Then again baz() is invoked, and a new execution context with its own variable environment, it's own memory space for its variables is created. Hence, when the variable foo is re-declared in baz(), it is only limited to this environment, and is different altogether.
  • In short, every time you call a function, you get your own execution context.
  • So even though, foo is declared 3 times, they are distinct, they are unique, they don't touch each other.
  • Therefore calling functions bar() and baz() wouldn't affect foo = 1 in the GEC. To prove that, we have consoled foo again at the very end of the program after calling bar(). Should it have an impact? The answer is NO!!.

This is how it is executed internally from execution context's point of view.
Alt Text

Execution Stack:

In computer science, a call stack is a stack data structure that stores information about the active subroutines of a computer program. This kind of stack is also known as an execution stack, program stack, control stack, run-time stack, or machine stack, and is often shortened to just "the stack". Wikipedia

A subroutine in simple words is nothing but what we call a function. Simply put, JavaScript has a single call stack in which it keeps track of what function we are currently executing & what function is to be executed after that. But first- what's a stack? A stack is an array-like data structure where you can add items(push) to the back and only remove(pop) the last item from it.

Woof!! those are some big words. I know it is difficult to digest the above piece of information, but be patient. Trust me, a piece of code and some visual diagrams will definitely make the concept clearer. So let's examine a piece of code.

// Global execution context

console.log("global execution context");

function foo() {
  console.log("foo is executing");
  console.log("foo has finished executing");
}

function bar() {
  console.log("bar is executing");
  foo();
  console.log("bar has finished executing");
}

function baz() {
  console.log("baz is executing");
  bar();
  console.log("baz has finished executing");
}

baz();
console.log("program successfully executed");

// Global execution context

// global execution context
// baz is executing
// bar is executing
// foo is executing
// foo has finished executing
// bar has finished executing
// baz has finished executing
// program successfully executed

Initially, before running any piece of javascript code, the execution stack is empty. When we run a javascript program/file, the javascript engine creates a global execution context and pushes it on top of the stack the moment your program starts executing.

In simple words, it is the context of your entire javascript program/source code or the context within which your entire code runs(as shown in the code snippet).

Given below is a simple illustration of how it really looks from the execution stack's perspective.
Alt Text

  • When the javascript interpreter comes to baz(), the point of time when baz() is called, a new execution context is created for baz() and is put on top of the execution stack.
  • Inside function baz(), statements are executed then (if any). The moment bar() is called inside baz(), a new execution context of bar() is created and is pushed to the top of the stack.
  • The interpreter then executes bar() and the statements inside of it until it encounters invoking function foo() and a new execution context is created and pushed on top of the stack.
  • In the above diagram, at any given point:

(i). The running execution context is always on top of the stack.
(ii). There can be at most one execution context running on the stack at a time.
(iii). The arrow(⇨) on the stack denotes the current execution context.

  • After foo() has finished execution, it is popped off from the stack & the control comes to the next line from where it was invoked i.e from bar().
  • Similarly, the process goes on until each and every execution context gets completed and is removed from the stack.
  • Finally, the GEC remains on the stack and is eventually popped off when the program completely finishes executing.

Scope Chain:

What if, an execution context has no definition of a variable which it wants to print? Consider the code snippet below. baz() has no variable declared as foo in its execution context.

// every execution context has a reference to it's outer environment

function baz(){
   /* foo's value is taken from the outer environment i.e. global environment */
   console.log(foo); //1
}

function bar(){
   var foo = 2;
   console.log(foo); //2
   baz();
}

var foo = 1;
bar();

'Every execution context has a reference to it's outer environment', all the way up to the global scope. This hierarchy/chain of reference is what is termed as Scope Chain. So if the current execution context has a variable definition missing, it accesses its outer environment to look out for the variable. In the above code, when the execution context of baz() couldn't find a variable foo inside its environment, it went searching for it in it's outer environment i.e. Global execution context and prints out it's value. This is what happened when the current execution context couldn't find a variable foo.
Alt Text

The outer environment of baz() happens to be the global execution context in our example. This is not the case every time. Consider the code snippet below:

// every execution context has a reference to it's outer environment

function bar(){
   var foo = 2;

   /* here baz's outer environment is the execution context of bar */
   /* and not the global execution context*/
   function baz(){
      console.log(foo); //2
   }

   baz();
}

var foo = 1;
bar();

Now, we have moved baz() inside the function bar(), i.e. it is now nested inside bar(). In this case, it's outer environment has now changed from the global execution context to the execution context of function bar(). So now instead of printing foo as 1(from GEC) it prints foo as 2(from bar's execution context).

Conclusion:

Execution context had always been confusing for developers at first, especially those who are new to JavaScript. Visualizing these concepts and how they work internally helps simplify things making it a piece of cake 🍰. Knowing these concepts will help you know how & why javascript works the way it does. Do let me know if you guys have any queries or feedback for improvement. Hope you like it. Happy learning...😊

Discussion (0)