Javascript Engine

Javascript Engine

A Javascript engine translates javascript to something a computer can understand. Anyone can make a JS Engine as long as they adhere to ECMAScript standards. For example, the V8 Engine, made by Google in 2008, was created to resolve the problem of rendering Google Maps efficiently. Before that, JS engines were very basic. Chrome browser and NodeJS use V8.

List of JS engines: https://en.wikipedia.org/wiki/List_of_ECMAScript_engines (ECMAScript is the governing body of Javascript that dictates standards of the language)

A high-level overview of the V8 engine:

Javascript V8 Engine

  • Parser

    • Breaks up code based on Javascript reserved words to create tokens to identify their meaning
  • AST (Abstract Syntax Tree)

  • Interpreter

    • Reads and translates code to bytecode line by line on run time
  • Profiler (AKA monitor)

    • Finds optimizations as the code runs on the fly and passes it to the compiler (for example if the same lines of code are run multiple times, but produce the same result)
  • Compiler (JIT)

    • Compiles down the code to machine code and replaces the byte code with its optimized equivalence flagged by the Profiler

So… is Javascript compiled or interpreted? It depends on implementation ;)

When JS was first rolled out it was only interpreted. It was slow because there were no optimizations: it ran the code it was given from start to finish. For example, if you loop 100 times over code that adds 4 to 3, a non-optimized engine would run that code and do the calculation 100 times, whereas a compiler would optimize for this possibly by running the code only once and replacing it with the number 7.

The compiler needs to compile first but ends up running faster afterward. The Interpreter runs right away but is inefficient. We can combine these methods to get the best of both worlds: JIT Compiler (Just In Time Compiler)

This process makes the code faster as it runs because more and more optimizations will be done via the Profiler and Compiler. How do we use this to our advantage to write efficient code? You should avoid using these Javascript features as much as possible because they make engine optimization difficult:

  • eval()
  • arguments
  • for in
  • with
  • delete

The reason has to do with inline caching and hidden classes. A summary:

  • Inline Caching

    • Code that executes the same function and produces the same result repeatedly is optimized by the compiler by getting cached.

      function add(a, b) {
        return a + b
      }
      
      for (let i = 0; i < 100; i++) {
        add(1, 2)
      }
      
      // When cached, instead of calling add 100 times, will be replaced with 
      for (let i = 0; i < 100; i++) {
        3
      }
      
  • Hidden Classes

    • Assigning object properties in differing orders will result in deoptimization. Either assign them during instantiation via the constructor or assign values in the same order.

      function Numbers(a, b) {
        this.a = a;
        this.b = b;
      }
      
      const n1 = new Numbers(1, 2);
      const n2 = new Numbers(3, 4);
      
      // This will result in deoptimization because the compiler will assume
      // They have different classes
      
      n1.a = 10
      n1.b = 20
      n2.b = 30
      n2.a = 40
      
    • The delete keyword also causes deoptimization by confusing the compiler about shared hidden classes.

More on optimization: https://richardartoul.github.io/jekyll/update/2015/04/26/hidden-classes.html

Part of Advanced Javascript: One topic a day