How JavaScript Works Behind the Scenes

Undoubtedly, JavaScript is by far one of the best dynamic programming languages whole over the tech industry. If you go far as a JavaScript Developer you need to know what happens behind the scene in JavaScript.


It’s very normal when you face some bug issues in your JavaScript Code. But If you predict the execution of your writing code then it’s easy to fix your bug. The interesting thing is when you are already known about JavaScript behind the scene then you can simply predict your code.

Actually, Computers, Compiler, or Browsers can’t understand any kind of source code. So, How it works? Behind the scene, there is an environment to run this source code. The most popular JavaScript environment is Browser & Node JS. If we run JavaScript code we must have Browser or Node js run time. Node js or Browser behind the scene has another important tool that is JavaScript Engine. There is a lot of popular JavaScript Engine according to the browser like Google Chrome ( v8 ),  Firefox (SpiderMonkey), and  IE, Edge (Chakra ).






Not related with Dom tree. it just a representation of our entire code inside the JavaScript Engine.










Every JavaScript Engine has a powerful and intelligent parser. This is works with the source code. If the source code is right in the parser then it is converted into ‘Abstract Window Tree or Data Structure’ after that converted into Machine Code, finally, the code is ‘Run’ and we will get our expected answer. See the example.


Execution Context:  Simply, Where a piece of code is being executed that is called Execution Context, and it’s an Object. The default execution context is either  ‘Window’  or  ‘Global’  Object. For example, If you create a variable that is store as a Global Object in JavaScript Engine. Keep in mind, when you write code in an editor that is ‘ Global Object’  and writing a code in Browser console that is  ‘Window Object’.





For more clarification.

    var name = 3;
    name == window.name; 
    //Output: true

But here is a dilemma, What about the code which is inside a function? Actually, whenever we call a new function it will get a brand new context for its own.

Execution Stack: Execution Stack is also known as "calling stack". It’s basically who the code executed step by step. This stack works with the concept of  Last In First Out (LIFO).


    var name = 'Zonayed';

    function first() {
       var welcome = 'Hello ';
       second();
       console.log(welcome + name);
    }

    function second() {
       var welcome2 = 'Hi! ';
       third();
       console.log(welcome2 + name);
    }

    function third() {
       var welcome3 = 'Hey! ';
       console.log(welcome3 + name);
    }

    first();



Execution Context Object: 
This ‘Object’ has three properties


  1. Variable Object — It contains function arguments, inner variable declarations, and function declarations.
  2. Scope Chain — It contains the current variable object and all the variable objects of all its parents.
  3. “This” Variable

Note that, When a function gets a call there creates a new execution context and it happens in a two-step.

  1. Creation Phase
    a) Create a Variable Object.
    b) Create Scope Chain.
  2. Execution Phase
    a) Determining the value of the “this” variable.
    b) Read & Run line by line
    c) Define All Variables


Variable Environment_ Hoisting and The TDZ


👉🏻 let and const variables ==> ReferenceError: Cannot access before initialization

👉🏻 var ==> undefined

👉🏻 var ==> var hoisting is just a byproduct of function declaration

***Accessing variables before declaration is a bad practice and should be avoided.
    // Variables //
    console.log(me); // returns: undefined
    console.log(job); // returns: ReferenceError: Cannot access 'job' before initialization
    console.log(year); // returns: ReferenceError: Cannot access 'job' before initialization

    var me = 'jonas';
    let job = 'teacher';
    const year = 1991;

    // Functions //
    console.log(addDecl(2, 3)); // returns: 5
    console.log(addExpr(2, 3)); // returns: ReferenceError: Cannot access 'addExpr' before initialization
    console.log(addArrow(2, 3)); // returns: ReferenceError: Cannot access 'addArrow' before initialization

    function addDecl(a, b){
        return a + b;
    }
    
    const addExpr = function(a, b){
        return a + b;
    }

    const addArrow = (a,b) => a + b;

    // Using var //
    var addExpr = function(a, b){
        return a + b; // returns: TypeError: addExpr is not a function
    }

    var addArrow = (a,b) => a + b; // returns: TypeError: addArrow is not a function

    // Reason: any variable declared with var will be hoisted and set to undefined and now this 'addExpr or addArrow' are undefined, we are trying to call undefined(2,3)
    // Example
    if(!numberProducts) deleteShoppingCart();

    var numberProducts = 10;

    function deleteShoppingCart(){
        console.log('All products deleted!'); 
    }

    // returns: All products deleted!
    // reason: numberProducts = 10 but we are using 'var' variable and we call it before declaration so var hoisting is undefined, so return is very DANGEROUS

    /*
    // Solutions //

    1. just dont use 'var' to declare variable
    2. use 'const' most of the time
    3. if need to change then use 'let'
    4. to write clean code should declare variable at the first
    5. always declare functions first and use them only after declaration
    6. you could use function declaration before you declare them but dont try this
    */
    var x = 1;
    let y = 2;
    const z = 3;

    console.log(x === window.x); // returns: true
    console.log(y === window.y); // returns: false
    console.log(z === window.z); // returns: false

    // variable declared with 'var' will create a window property

Scope and The Scope Chain






Scoping in Practice
    'use strict';
    const firstName = 'Jonas'; // <== Global variable
    function calcAge(birthyear) { // <== Scope
        const age = 2037 - birthyear; // <== Local variable | if we call return here, variable not needed

        function printAge() {
            let output = `${firstName} You are ${age} years old, born in ${birthyear}`;
            console.log(output);

            if (birthyear >= 1981 && birthyear <= 1996) { // <== Block scope
                // Creating NEW variable with same name as outer scope's variable
                let firstName = 'Steven'; // <== JavaScript use this variable and don't lookup for similer global variable

                // Reassigning outer scope's variable
                output = 'NEW OUTPUT!'; 

                var millenial = true; // <== Keep in mind when working with Old codes
                const str = `Oh, and you're a millenial ${firstName}`;
                console.log(str);

                function add(a, b) {
                    return a + b;
                }
            }
            console.log(output)

            /*
            add(2, 3); // <== calling a function outside of function because 'strict' mode is disabled
            console.log(add(2, 3));
            */
            console.log(millenial);
            // concole.log(str) <== var is a functional scope, we can acces this scope outside of a function
        }
        printAge();

        return age;
        console.log(age); // <== After return code will not run
    }


    calcAge(1991); //
    // console.log(age); <== We can't access inside function
    // printAge(); <== 

    /*
    const firstName = 'Steven'; <== Local variable
    const firstName = 'Jonas'; <== Global variable

    // No problem with that those are completely different variable just similer name

    // You can also declare similer perameter name, because each parameter is associated with each function
    */


The this keyword


The 'this' Keyword in Practice

    'use strict';
    console.log(this); // returns: window | global object

    const calcAge = function (birthyear) {
        console.log(2037 - birthyear);
        console.log(this) // returns: undefined | if strict mode off ==> returns: window
    };
    calcAge(1994);

    const calcAgeArrow = birthyear => {
        console.log(2037 - birthyear);
        console.log(this) // returns: window
    };
    calcAgeArrow(1994);
    // Arrow function use 'Lexical this' | parent scope

    const jonas = {
        year: 1994,
        calcAge: function () {
            console.log(2037 - this.year);
            console.log(this) // returns: jonas object
        }
    }
    jonas.calcAge();

    // This is point to the Object that is calling the Method
    // We might thing we wrote calcAge method inside of jonas object, that's why 'this' is point to jonas ==NO==
    // The reason why 'this' keyword is point to jonas object just because jonas is calling the Method
    
    const matilda = {
        year: 2017,
    };
    // Method Borrowing
    matilda.calcAge = jonas.calcAge; // Copy Method to one object to other
    matilda.calcAge(); // calling function

    // Method Borrowing //
    // copy method from one object to other

    // *** 'This' keyword pointing on ==> who is 'calling' the method

*** 'this' keyword pointing on ==> who is 'calling' the method

*** This is NOT static. It depends on how the function is called, and its value is only assigned when the function is actually called


Regular Functions vs Arrow Functions

    const jonas = {
        firstName: 'Jonas',
        year: 1991,
        calcAge: function() {
            console.log(this); // returns: Jonas object
            console.log(2037 - this.year);
        },
        greet: () => console.log(`Hey ${this.firstName}`), // returns: Hey undefined
        // Arrow function() does not get its own 'this' keyword
    };
    jonas.greet();

    console.log(this) // returns: window object
    console.log(this.firstName) // undefined
    // Reason: On the window object there is no 'firstName' property so, returns: undefined


Again, What if

    var firstName = 'Matilda';
    //if we declare variables with 'var' it create global property on window/global object
    console.log(this) // window object
    window.firstName = 'Matilda';

    const jonas = {
        firstName: 'Jonas',
        year: 1991,
        calcAge: function() {
            console.log(2037 - this.year);
        },
        greet: () => console.log(`Hey ${this.firstName}`), // returns: Hey Matilda
    };
    jonas.greet();


Solution | Use Regular Function

    const jonas = {
        firstName: 'Jonas',
        year: 1991,
        calcAge() {
            console.log(this); // returns: Jonas object
            console.log(2037 - this.year);
        },
        /*
        greet: () => console.log(`Hey ${this.firstName}`), // returns: Hey undefined
        // Arrow function() does not get its own 'this' keyword
        */
        greet: function () {
            console.log(this); // returns: Jonas object
            console.log(`Hey ${this.firstName}`) // returns: Hey Jonas
        },
    };
    jonas.greet();

***BEST PRACTICE - You should never use Arrow Function as a Method.


Method vs Function

Method and a function are the same, with different terms. A method is a procedure or function in object-oriented programming.


A function is a group of reusable code which can be called anywhere in your program. This eliminates the need for writing the same code again and again. It helps programmers in writing modular codes.


Function inside a Function, Using 'this' keyword

    const jonas = {
        firstName: 'Jonas',
        year: 1991,
        calcAge: function () {
            console.log(this); // returns: Jonas object
            console.log(2037 - this.year); // returns: 46

            const isMillenial = function(){
                console.log(this) // returns: undefined
                console.log(this.year >= 1981 && this.year <=1996);
                // returns: TypeError: Cannot read property 'year' of undefined
            };
            isMillenial();
        },
        greet: function () {
            console.log(this); // returns: Jonas object
            console.log(`Hey ${this.firstName}`) // returns: Hey Jonas
        },
    };
    jonas.greet();
    jonas.calcAge(); // function inside a method


Solution 1 | Pre ES6 Solution

    const jonas = {
        firstName: 'Jonas',
        year: 1991,
        calcAge: function () {
            console.log(this); // returns: Jonas object
            console.log(2037 - this.year); // returns: 46

            // Solution 1 | Pre ES6 solution //
            const self = this; // self or that 
            const isMillenial = function () {
                console.log(self); // returns: jonas object
                console.log(self.year >= 1981 && self.year <= 1996);
                // console.log(this.year >= 1981 && this.year <= 1996);
            };
            isMillenial();
        },
        greet: function () {
            console.log(this); // returns: Jonas object
            console.log(`Hey ${this.firstName}`) // returns: Hey Jonas
        },
    };
    jonas.greet();
    jonas.calcAge(); // function inside a method


Solution 2 | Modern solution using Arrow Function

    const jonas = {
        firstName: 'Jonas',
        year: 1991,
        calcAge: function () {
            console.log(this); // returns: Jonas object
            console.log(2037 - this.year); // returns: 46

            // Solution 1 | Pre ES6 solution //
            /*
            const self = this; // self or that 
            const isMillenial = function () {
                console.log(self);
                console.log(self.year >= 1981 && self.year <= 1996);
                // console.log(this.year >= 1981 && this.year <= 1996);
            };
            */
            // Solution 2 | Modern solution using Arrow Function //
            const isMillenial = () => {
                // Arrow function inherit 'this' keyword from its parent scope
                console.log(this);
                console.log(this.year >= 1981 && this.year <= 1996);
            };
            // Arrow Function inherit 'this' keyqord from parent scope

            isMillenial();
        },
        greet: function () {
            console.log(this); // returns: Jonas object
            console.log(`Hey ${this.firstName}`) // returns: Hey Jonas
        },
    };
    jonas.greet();
    jonas.calcAge(); // function inside a method


Arguments Keyword

👉🏻 Arguments keyword exists but it only exists in the Regular function but not in the arrow function.

👉🏻 Arguments keyword is not important in Modern JavaScript anymore because now we have a Modern Way that dealing with multiple parameters.

    const addExpr = function (a, b) {
        console.log(arguments) // returns: arguments array
        return a + b;
    };
    addExpr(2, 5);
    addExpr(2, 5, 8, 12); // we can add more arguments

    const addArrow = (a, b) => {
        console.log(arguments) // returns: ReferenceError: arguments is not defined
        return a + b;
    };
    addArrow(2, 5, 8)

Primitives vs. Objects (Primitive vs. Reference Types)

    // Primative types

    let age = 30;
    let oldAge = age; // at this point age still 30
    age = 31; // now age changed but it will not effect oldAge variable
    console.log(age) // returns: 31
    console.log(oldAge) // returns: 30


    // What if,    
	1 === 1 // returns: true, value same
    [1] === [1] // returns: false, it just compare reference
    {1} === {1} // returns: false, it just compare reference

    // Reference types

    const me = {
        name: 'Jonas',
        age: 30,
    }
    const friend = me;
    friend.age = 27;
    // same as
    me.age = 28
    console.log('Friends:', friend); // returns: { name: 'Jonas', age: 27}
    console.log('Me', me);
    // returns: { name: 'Jonas', age: 27}

    // 'const' variable are not changeable in PRIMATIVE values
    // but NOT changeable in REFERENCE values

    // When we declare a variable as an object, an indentifier is created which point to piece of memory in STACK which turn point to piece of memory in the HEAP

Primitive data types

Javascript has 7 data types that are passed by Value: Boolean, null, undefined, String, and Number, BigInt, Symbol. We’ll call these primitive types.

A primitive data type has a fixed size in memory.

A number occupies eight bytes of memory and a Boolean value can be represented with only one bit.

Reference data types

Except 7 Primitive values, all data types of JavaScript are passed by reference: Array, Function,  Object, Map, Set, Date, Regex Almost everything that made with 'new' keyword. These are all technically Objects, so we’ll refer to them collectively as Objects

Reference data types do not have a fixed size in memory.

So their value cannot be stored directly in the eight bytes of memory associated with each variable.

Typically, this reference is some form of pointer or memory address. It is not the data value itself, but it tells the variable where to look to find the value.




    // Primitives Types
    let lastName = 'Williams'; // new value
    let oldLastName = lastName; // copied value
    lastName = 'Davis'; // reassign value - present value
    console.log(lastName, oldLastName); // Davis Williams

    // Reference Types
    const jessica = {
        firstName: 'Jesica',
        lastName: 'Williams',
        age: 27,
    }; // Jesica got married and have to changed his lastName

    const marriedJessica = jessica;
    marriedJessica.lastName = 'Davis'

    console.log('Before Marriage: ' , jessica);
    // returns: {firstName: 'Jesica', lastName: 'Williams', age: 27}
    console.log('After Marriage: ', marriedJessica);
    // returns: {firstName: 'Jesica', lastName: 'Williams', age: 27}

    // It will not changed 😟
    // Because: when we are attemped to copy original jesica object, NOT create a new object in the HEAP. marriedJessica is not a new object in the HEAP, it just a simple another variable in the STACK to hold the reference, Both of this two variable simply indicate the same stored memory address in the HEAP. Two different variable but same memory address.

    // Assinging a new object to it is completely different to change property

    // Copying object
    const jessica2 = {
        firstName: 'Jesica',
        lastName: 'Williams',
        age: 27,
        family: ['Alice', 'Bob'] // Nested Reference Value
    };

    const jessicaCopy = Object.assign({}, jessica2); // OR const jessicaCopy = {...jessica2}
    jessicaCopy.lastName = 'Davis'

    console.log('Before Marriage: ' , jessica2);
    // returns: {firstName: 'Jesica', lastName: 'Williams', age: 27}
    console.log('After Marriage: ', jessicaCopy);
    // returns: {firstName: 'Jesica', lastName: 'Davis', age: 27}

    // THERE IS A PROBLEM: Nested Reference Value will not Copy IMMUTABLY if there make any changes

    jessicaCopy.family.push('Mary');
    jessicaCopy.family.push('John');

    // Deep Clone Library 🧲LODASH

Post a Comment

0 Comments