DEV Community

Cover image for My Javascript Execution Context Journey
Ibrahim Elhofy
Ibrahim Elhofy

Posted on • Edited on

My Javascript Execution Context Journey

Introduction

Execution Context is a concept if you understand it, will make it easier for you to understand more deeper concepts like :

  • Scope
  • Scope Chain
  • Closure
  • Event Loop

So in this article we will dive in this mysterious concept to understand what really happens under the hood in addition to digging a little bit deeper to know how Execution Context works with Web APIs, let's go.

Execution Context

When a fragment of JavaScript code runs, it runs inside an execution context which is an abstract concept of an environment where the Javascript code is evaluated and executed By environment, we mean the value of this, variables, objects, and functions JavaScript code has access to at a particular time.

When is an Execution Context created ?

There are three types of code that create a new execution context:

  • Global Execution Context (GEC) This is the default or base execution context When a script executes for the first time, the JavaScript engine creates it. All of the global code ( which is not inside any function or object is executed inside the global execution context ). GEC cannot be more than one because only one global environment is possible for JS code execution as the JS engine is single threaded.

Hint: In the Browser the Global Execution Context is created if there is no javascript script in the page or even when you do not have a single line of code in a .js file and load it, you will have the Global Execution Context created.

  • Function Execution Context It is an Execution Context like similar Global Execution Context gets created when function is invoked, but instead of creating the global object, it creates the arguments object that contains a reference to all the parameters passed into the function:

  • Eval Execution Context The underrated type in this Execution Context any code executed inside it also gets its own execution context, but because eval is underrated I will ignore it too and in the other hand it is also not recommended, so I will not discuss it here.

Execution Context Stack

When Execution Context is executing it is pushed into Execution Contexts Stack after it terminates, it popped off from the stack and this a whole process makes the javascript engine able to keep track off running function, or processes.

The way that Execution Context Stack works by can solve you the mysterious of how javascript is single threaded that is because you can't put multiple execution context in Execution Context Stack in the same time so you do one thing at run time

So the execution context stack can be conceptually represented as follows:

ExecutionContextStack [

  ExecutionStack <global> { ... }

]
Enter fullscreen mode Exit fullscreen mode

When you execute your script the javascript engine creates a new Execution Context called Global Execution Stack we will examine it deeper later in this article, then it pushes it in our Execution Context Stack

Execution Context Phases

Our Execution Context his life cycle walks through two phases:

The Creation Phase

In this phase The execution context is created. which the JS engine is in the compilation phase and it just scans over the code to compile the code, it doesn’t execute any code and in our Creation Phase there is a lot of things happen.

  1. LexicalEnvironment component is created.
  2. VariableEnvironment component is created.

Lexical Environment

According to ECMAScript 6 specification:

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code
Let’s try to simplify a few things here. A lexical environment consists of two main components: the environment record and a reference to the outer (parent) lexical environment:

Simply put, A Lexical Environment it's the internal Javascript Engine construct for let-defined variables. If you define a variable with let in a function , it is only visible within the function.

To keep all things simple let's put what we have just learned into code.

let globalLet = 1;
const globalConst = 2;

function globalFunction () { ... }
Enter fullscreen mode Exit fullscreen mode
ExecutionContextStack [

  ExecutionContext <global> {

    lexicalEnvironment {
       globalLet : <uninitialized>,
       globalConst : <uninitialized>,
       globalFunction : <ref to "globalFunction" function>
    }

  }
]
Enter fullscreen mode Exit fullscreen mode

Each Lexical Environment has three components:

  • Environment Record
  • Reference to the outer environment,
  • This binding.

Environment Record

You can think of it like a dictionary in which declarations of variables and functions are stored within the lexical environment.

let globalLet = 1;
const globalConst = 2;

function globalFunction () { ... }
Enter fullscreen mode Exit fullscreen mode
ExecutionContextStack [

  ExecutionContext <global> {

    lexicalEnvironment {

       EnviromentRecord {
          globalLet : <uninitialized>,
          globalConst : <uninitialized>,
          globalFunction : <ref to "globalFunction" function>
       }
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

There are three type subclasses inside of the Environment Record:

  • Declarative environment record — As its name suggests, it stores variables, modules and classes, in addition to function declarations, you can think about this concept as instantiations of something you know, like maps or tables to store declared objects in it.
  • Object environment record — It is an Environment Record that associated with an object called binding object and for each of its properties a corresponding entry is created in the OER.

That is why you can access global variables such as window.document that is because window object is associated global execution context's object environment record any mutation on any on window applies on the record

let globalLet = 1;
const globalConst = 2;

function globalFunction () { ... }
Enter fullscreen mode Exit fullscreen mode
ExecutionContextStack [

  ExecutionContext <global> {

    lexicalEnvironment {

      EnviromentRecord {
        [[ type ]] : 'declarative'
        globalLet : <uninitialized>
        globalConst : <uninitialized>
        globalFunction : <ref to "globalFunction" function>
      }

      // binding with `window` object
      EnviromentRecord {
        [[ type ]] : 'objective'
        globalFunction : <ref to "globalFunction" function>
      }
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

Reference to the Outer Environment

Every Environment Record has an [[ outerEnv ]] a reference to the outer environment ( the environment from which it was called ) to make the javascript engine able to look for variables inside the outer environment if they are not found in the current lexical environment

let globalLet = 1;
const globalConst = 2;

function globalFunction (localArgument) {
  let localLet = 3;
  this.localProperty = 4;
}

new globalFunction(5);
Enter fullscreen mode Exit fullscreen mode
ExecutionContextStack [

  ExecutionContext <globalFunction> {

    lexicalEnvironment {

      EnviromentRecord {
        [[ type ]] : 'declarative'
        arguments : { 0 : <ref to "localArgument" argument>, length : 1 }
        localLet : 3
      }

      // binding with `arguments` object
      EnviromentRecord {
        [[ type ]] : 'objective'
        localArgument : undefined
      }

      // binding with `this` object
      EnviromentRecord {
        [[ type ]] : 'objective'
        localProperty : undefined
      }

      this: <ref to "globalFunction" object>

    }

  }

  ExecutionContext <global> {

    lexicalEnvironment {

      EnviromentRecord {
        [[ type ]] : 'declarative'
        window : <ref to "window" object>
        globalLet : <unintialized>
        globalFunction : <ref to "globalFunction" function>
      }

      // binding with `window` object
      EnviromentRecord {
        [[ type ]] : 'objective'
        globalFunction : <ref to "globalFunction" function>
        fetch : <built-in function>
        setTimeout : <built-in function>
      }

      this: <ref to "window" object>

    }

  }

]
Enter fullscreen mode Exit fullscreen mode

This Binding

  • In the global execution context the value of this refers to the global object ( in browsers, this refers to window object )
  • If it is called by an object reference, then the value of this is set to that object, otherwise, the value of this is set to the global object or undefined(in strict mode). For example:

this refers to the reference object and it's value depending on variety variables, so :

  • In Global Execution Context it refers to the global object ( window in browser or global in node js )
  • In Functional Execution Context it's referencing depends on the way that function call it. if it called by an object reference, then the value of this is set to that object. Otherwise, the value of this is set to the window object or will be of value undefined ( in strict mode )
let globalLet = 0;

function globalFunction (localArgument) {
  let localLet = 1;
  this.localProperty = 2
}

new globalFunction(3);
Enter fullscreen mode Exit fullscreen mode
ExecutionContextStack [

  ExecutionContext <globalFunction> {

    lexicalEnvironment {

      EnviromentRecord {
        [[ type ]] : 'declarative'
        arguments : { 0 : <ref to "localArgument" argument>, length : 1 }
        localLet : <unintialized>
      }

      // binding with `arguments` object
      EnviromentRecord {
        [[ type ]] : 'objective'
        localArgument : undefined
      }

      // binding with `this` object
      EnviromentRecord {
        [[ type ]] : 'objective'
        localProperty : undefined
      }

      this: <ref to "globalFunction" object>

    }

  }

  ExecutionContext <global> {

    lexicalEnvironment {

      EnviromentRecord {
        [[ type ]] : 'declarative'
        window : <ref to "window" object>
        globalLet : <unintialized>
        globalFunction : <ref to "globalFunction" function>
      }

      // binding with `window` object
      EnviromentRecord {
        [[ type ]] : 'objective'
        globalFunction : <ref to "globalFunction" function>
        fetch : <built-in function>
        setTimeout : <built-in function>
      }

      this: <ref to "window" object>

    }

  }

]
Enter fullscreen mode Exit fullscreen mode

Variable Environment:

It's also a Lexical Environment so it has all the properties and components that defined above, the purpose beyond that is storing the variable (var) bindings only.

var globalVariable = 0;
Enter fullscreen mode Exit fullscreen mode
ExecutionContextStack [

  ExecutionContext <global> {

    lexicalEnvironment { . . . }

    variableEnvironment {

      // binding with `window` object
      EnviromentRecord {
        [[ type ]] : 'objective'
        globalVariable : undefined
      }

      this: <ref to "window" object>

    }

  }

]
Enter fullscreen mode Exit fullscreen mode

Execution Phase

After Creation Phase our Execution Context walks through the Execution Phase to bind variable initializations, variable assignments, mutability and immutability checking, variable binding deletions, function call execution, etc.

let globalLet = 0;

function globalFunction(localArgument) {
  var localVariable = 2;
  this.localProperty = 3
}

new globalFunction(4);
Enter fullscreen mode Exit fullscreen mode
ExecutionContextStack [

  ExecutionContext <globalFunction> {

    lexicalEnvironment {

      EnviromentRecord {
        [[ type ]] : 'declarative'
        arguments : { 0 : <ref to "localArgument" argument>, length : 1 }
      }

      // binding with `arguments` object
      EnviromentRecord {
        [[ type ]] : 'objective'
        localArgument : 4
      }

      // binding with `this` object
      EnviromentRecord {
        [[ type ]] : 'objective'
        localProperty : 3
      }

      this: <ref to "globalFunction" object>

    }

  }

  ExecutionContext <global> {

    lexicalEnvironment {

      EnviromentRecord {
        [[ type ]] : 'declarative'
        window : <ref to "window" object>
        globalLet : 0
        globalFunction : <ref to "globalFunction" function>
      }

      // binding with `window` object
      EnviromentRecord {
        [[ type ]] : 'objective'
        globalFunction : <ref to "globalFunction" function>
        fetch : <built-in function>
        setTimeout : <built-in function>
      }

      this: <ref to "window" object>

    }

  }

]
Enter fullscreen mode Exit fullscreen mode

At the end I advice you to read this article several times to fully understanding these all concepts

Thanks

Sources

Top comments (0)