Javascript engines are programs that convert Javascript code into native machine code. These engines are embedded into the browser for runtime compilation and execution of the code. Google chrome uses V8 engine, Safari uses JavaScriptCore, and Firefox  uses  SpiderMonkey. To transform and run code faster, modern javascript engines use Just In Time (JIT) compilation, which is a combination of both interpretation and compilation.

How JS Engine Works

img

  1. Parser

    When Javascript code is executed, the engine first parses the code and breaks it down into token. These tokens are then converted into Abstract Binary Tree: a tree representation of the source code. AST Explorer is a tool that shows how code is converted into AST.

  2. Interpreter

    The interpret in V8 is called Ignition. The interpreter reads and translates the JavaScript files line by line on the fly, based on the generated AST. It produces unoptimised bytecode.

  3. Profiling and Optimisation

    The JavaScript compilers run feedback and collect profiling data for the code being executed. It looks for code that is hot: code that is run quite often or on which most of the exeuction time is spent. This code goes through optimizing compiler. It then generates optimised machine code that will be replaced for execution. V8 uses an optimising compiler called TurboFan. Turbojet can optimise the bytcode, produced by the Interpreter, in the background as the application runs.

Call Stack and Heap

img

A JS Engine consists of two components:

  • Call Stack
  • Heap

The heap is where objects are stored and memory allocation happens. Call stack keeps track of where the code is currently being executed using execuction contexts. When a function starts executing, its execution context is pushed on top of the stack and when it finishes execution, it is popped off the stack.

Execution Contexts

Execution context is an environment in which a piece of javascript code is executed. It stores all the necessary information like variables, functions and objects to execute the code.

  • Global Execution Context

    It is the default execution context in which code starts execution and every Javascript code has exactly one global execution context. Global execution context is popped off the call stack when the entire programs finishes execution.

  • Functional Execution Context

    Each function has its own execution context. When executing the code, if JS finds a function call, it creates a new execution context for that function and this execution context is pushed on top of the call stack. Functional execution context has access to the global execution context.

What is inside an Execution Context?

  1. Variable Environment

    Variable Environment consists of the following information:

    • let, const and var declarations
    • Functions
    • Arguments object (not in arrow functions)

    In the creation phase, the code is scanned for variable declarations, and for each variable, a new property is created in the variable environment. This is called hoisting and this allows some types of variables accessible in code before they are declared. But hoisting doesn’t work the same way for every type of variables:

    • Function declarations are hoisted and initialised with the value of the actual function.
    • Declarations that are made using var are hoisted and initilaised with a default value of undefined. Trying to access any variable declared with var before it is defined, will not result in an exception and will have a value of undefined.
    • Declarations made using let and const are not initialized as part of hoisting and they remain in Temporal Dead Zone(TDZ). TDZ for every variable defined with const/let starts at the beginning of its scope until the line in which they are defined. Trying to access any variable declared with let/const before it is defined results in an exception.
    • Hoisting of function expressions and arrow functions depends on if they were created using var or let/const.
  2. Scope Chain

    Every scope has access to all the variables from its outer scope. This chain of reference is what we call a scope chain. This is the reason we can access global variables from local scopes.

  3. this keyword

    this variable is a special variable that is created for every execution context and its value depends on how a function is called.

    • When used in methods, this points to the object that is calling the method.
    • When a function is called as a regular function call, this will be undefined in strict mode. If not in strict mode, it will point to the window object.
    • Arrow functions do not have their own this keyword. It inherits from its parent function or surrounding function: lexical this keyword. If the parent scope of the arrow function is global scope, its value will be window object.
    • When a function is called in an event listener, this will point to the DOM element that the handler is attached to.