JavaScript – The Story Of The 2 Phases

There are 2 phases when JavaScript starts to run, and they are called the creation phase and the execution phase. in the creation phase, JavaScript goes over the first time over the code and arranges it in memory. in the execution phase, JavaScript goes again over the code and runs it.

The JavaScript engine creates for us a “global execution context” and Each time we run a function, an “execution context” is created on top of the “global execution context” and added to the call stack. if inside the function that runs there is another function that runs then another “execution context” is created and added to the stack. when the function is finished, it will pop out from the call stack. so always when we run code in JavaScript, it will be part of the execution context (inside global or inside a function).

Global Execution Context

In the creation phase, the global execution context creates for us 2 main things:

  • Global Object
  • this

In the global execution context, the “this” keyword and the “Global Object ” are the same and they both point to the window object.

When we create a variable or a function inside the global execution context it will be added to the window object (global object)

Lexical Environment

Lexical environment means that where we write something is important, The engine read through our code and checks where we define the functions and it will take actions accordingly. functions can be defined on the global and inside another function and it means that according to the place they have written they will have specific access to variables. this is called lexical scope.

Each lexical environment has a lexical scope that gives access to variables where the function was defined and not where the function is called.

Hoisting

In the creation phase, there is another step called hoisting. Hoisting is when JavaScript reads the code and arranges it in a way that moves function declarations and variables to the top of their respective environments. functions are hoisted and variables are partially hoisted. it happens in each of the execution contexts that are being created when we call/invoke a function.

If we will try to call a function (declaration function) before it is defined, it will work because the function is hoisted to the top above all and so we have access to it. if we will try to access a variable before it is declared (with a var keyword), we will get undefined because it’s partially hoisted which means that the variable is hoisted to the top but the assignment to the variable hasn’t been done yet, it will happen in the execution phase when code is running. so when we will try to access the variable before the assignment, we will get undefined which is the default value for declared variables without any assignment. if the variable is defined with const and we will try to access it, we will get an error.

The variables and functions are not really hoisted to the top, this explanation is to help us understand what’s happening in the creation phase. what it does is reserve memory for the variables and function declarations. and this gives us access to them when the code is about to run in the second phase.

Execution Context

In the execution phase, when we invoke a function an execution context is created. the execution context creates for us a few things:

  • this
  • arguments
  • variable environment
  • link to the outer environment

Arguments

The argument is like an array object which contains the parameters that we pass to the function. it’s not recommended to use it since it is like an array object and we can do many things with it which might make the compiler or the JavaScript engine less able to optimize our code.

In modern JavaScript, we can use “Array.from” to convert the arguments to an array or we can use rest parameters by passing the spread operator inside the function parenthesis.

Variable Environment

The variable environment is the place where the variables that are created inside the function can live. including the variables that are created outside the function (in the outer function) and that are being used inside the function. this last

Link to Outer Environment

Besides having an access to the variables that we create inside a function, we also have access to variables that are being used inside the function and which have been defined in the outer function (lexically). this behavior is called scope chaining.

“this” keyword

The “this” keyword refers to the object that is called the function.

In the example below there is no object that is calling the function, so it means that the function is invoked on the global window object so “this” refers to the window object:

example 1

In the example below there is no object that invokes the function, so the ‘this’ keyword refers to the window object:


function printThis() {
  console.log(this)  // Window Object.
}

printThis();  // This invocation is the same as window.printThis();


function printThisAgain() {
  'use strict'       // 'use strict' have been added to JavaScript to avoid common mistakes with JavaScript.
  console.log(this)  // undefined.
}

printThisAgain();

example 2

In the example below there is an object that invokes the function so the “this” keyword refers to the object that invokes it:


// This is basically the reason why the 'this' keyword was created in JavaScript.

PersonObject = {
  firstName: 'Nisan',
  lastName: 'Sabag',
  getFullName: function() {
    return this.firstName + ' ' + this.lastName;
  }
}

PersonObject.getFullName();  // 'Nisan Sabag'.

This behavior is not similar to anything in JavaScript which we talked about until now. until now we talked about the lexical scope of the lexical environment, how the compiler knew right away without running the code what variables the functions have access to (in the first phase). with the “this” keyword, the compiler does not know what “this” is until the code runs (in the second phase). so it does not matter really where we write the code (lexically), what matters is how the code is getting called.

The examples below will help us prove that the “this” keyword is not lexically scoped, that it doesn’t matter where it is written, it matters how the function was called:

example 3


const a = function() {
  console.log('a', this);
  const b = function() {
    console.log('b', this);
    const c = {
      hi: function() {
        console.log('c', this); 
      }
    };
    c.hi();
  };
  b();
};

a();
// Will print: Window, Window, Object

example 4


someObject = {
  doIt: function() {
    console.log(this);  //  Object.
    var doThat = function() {
      console.log(this);  //  Window.
    }
    doThat();  // Notice that the function does not invoked by an object.;
  }
}

someObject.doIt();

The key takeaway here is that the “this” keyword is dynamically scoped. it does not matter where the function is written, it matters how the function was called.

There are ways to solve this issue:

  1. arrow function – arrow functions are lexically bound, which means that they have lexical “this” behavior.
  2. using the bind method.
  3. we can save outside the function a reference to this keyword that points to the object