Overview

Javascript was originally designed as a simple scripting language for web browsers. Nowadays ECMAScript (Specification of Javascript) is a fully-featured general-purpose programming language.  It empowers a wide spectrum of different purpose applications. We can even find JS solutions in Space.

This article gives outline of some JS language fundamentals, that can help improve your code.

Engines

There are few JS engines which in different way resolve ECMASCRIPT implementation.

List of JS engines and browser usage

The main engine features are: compilation, run and code optimization.

Javascript Engine Pipeline

An engines follow in high level same steps:

  • Parser build AST (lexical, syntactic, semantic analysis)
  • Interpreter generates Bytecode
  • Bytecode is translated with JIT (Just In Time) compiler into machine code (only popular code)

Javascript Virtual Machine how it works?

JS Optimization - Engine trade-offs

All JS engines have to deal with tradeoffs between Low and High code optimization.

Only once function becomes hot (often used) it is optimized by the compiler. The main reason why JS engines optimize only hot functions is that:

  • More compiled code => More memory used
  • Compiling code => Extra time
  • Less compilation => Slower run

JS High level code, Bytecode, Machine code comparison

Objects

Each object is:

  • Internally defined as dictionaries.
  • Each object variable:
    • Has a defined name in string form
    • Contain additional property values:
PropertyExample valueDescription
Value“some value”Value
[[Writable]]trueAllow to change the value of the variable
[[Enumerable]]trueAllow to iterate over a variable (eg. in a loop)
[[Configurable]]trueAllow to delete an object variable

To check it, we can use Object.getOwnPropertyDescriptors function:

Object.getOwnPropertyDescriptors({key1: 'val1', key2: 'val2'})

Result:

{}

key1: {}
configurable: true
enumerable: true
​​value: "val1"
​​writable: true
​​<prototype>: Object {  }

key2: {}
​​configurable: true
​​enumerable: true
​​value: "val2"
​​writable: true
​​<prototype>: Object {  }

<prototype>: Object {  }

Arrays

  • Similar to objects + auto-updating length property
  • All values in the array field have the same internal properties.

Let’s see how property descriptors look for the array:

Object.getOwnPropertyDescriptors(['val1', 'val2'])

Result:

{}

0: {}
​​configurable: true
​​enumerable: true
​​value: "val1"
​​writable: true
​​<prototype>: Object {  }

1: {}
​​configurable: true
​​enumerable: true​​
value: "val2"
​​writable: true
​​<prototype>: Object {  }

length: {}
​​configurable: false
​​enumerable: false
​​value: 2
​​writable: true
​​<prototype>: Object {  }

<prototype>: Object {  }

Hidden Classes / Shapes / Structures / Types / Maps

When dynamically instantiating an object, these made in the background hidden class / shape / structure / type / map.

The structure instead of object value has Offset:

PropertyExample value
Offset0
[[Writable]]true
[[Enumerable]]true
[[Configurable]]true

Different terminology

Each engine has a different name for the same thing:

  • Academic => Hidden classes
  • JavaScriptCore => Structures
  • Chakra => Types
  • V8 => Maps
  • SpiderMonkey => Shapes

Let’s call it: “shape” in this article.

How does it work?

  • When dynamically instantiating an object, these made in the background object shape
  • Objects with the same values can have the same internal shape (Order is important)
  • Each object variable has a pointer to index or offset of the memory location where the value of the variable is written. JS engines shapes
  • Empty object => Empty shape
  • Adding variables => Building new shapes on the fly
  • Object shape transition is maintained (due to object mutation)
    JS transition chain
  • In case we have identical parent objects transition chain changes to transition tree
  • Chained shapes increase access time. To avoid it internal shape table is created which connects variable to a specific shape of an object.
  • Some engines clear the offset field when deleting a single variable, which slows down the access times

Inline caching

Inline Caching - When calling function, interpreter remembers the location of the shape from the object and memory offset of value. It enables faster accesses. Caching is not effective if sequentially access different objects.

Types

  • Monomorphic IC - objects accessed in the sequence have the same shape.
  • Polymorphic IC - objects that are accessed in the sequence have a different shape (4 or less).
  • Megamorphic IC - objects that are accessed in the sequence have a different shape (4 or more), code optimization turned off.

Optimization

Let’s imagine Megamorphic IC situation that we call the function with different shape every function call. Also, all 5 shapes have some common keys:

<html>
  <script type="text/javascript">
    function perfTest() {
      var t0 = performance.now();

      const X1 = { a: "A", b: "A", c: "A" };
      const X2 = { a: "B", b: "B", d: "B" };
      const X3 = { a: "C", b: "C", e: "C" };
      const X4 = { a: "D", b: "D", f: true };
      const X5 = { a: "E" };

      const object = [X1, X2, X3, X4, X5, X1, X2, X3];
      const get_a = (obj) => obj.a;

      for (var i = 0; i < 10000000000; i++) get_a(object[i & 7]);

      var t1 = performance.now();

      document.write("Execution took " + (t1 - t0) + " milliseconds.");
    }

    window.onload = function () {
      perfTest();
    };
  </script>
</html>

Execution time:

Execution took 26 9154.5499999993 milliseconds.

Now let’s try to optimize code to make the same shape from all 5 objects by adding null values to not covered keys:

 <html>
  <script type="text/javascript">
    function perfTest() {
      var t0 = performance.now();

      const X1 = { a: "A", b: "A", c: "A", d: null, e: null, f: null };
      const X2 = { a: "B", b: "B", c: null, d: "B", e: null, f: null };
      const X3 = { a: "C", b: "C", c: null, d: null, e: "C", f: null };
      const X4 = { a: "D", b: "D", c: null, d: null, e: null, f: true };
      const X5 = { a: "E", b: null, c: null, d: null, e: null, f: null };

      const object = [X1, X2, X3, X4, X5, X1, X2, X3];
      const get_a = (obj) => obj.a;

      for (var i = 0; i < 10000000000; i++) get_a(object[i & 7]);

      var t1 = performance.now();

      document.write("Execution took " + (t1 - t0) + " milliseconds.");
    }

    window.onload = function () {
      perfTest();
    };
  </script>
</html>

Execution time:

Execution took  3 1370.180000005348 milliseconds.

Optimized code execution time drastically decreased in comparison of unoptimized. From 26 sec to 3 sec.

Prototypes

  • Prototypes are specially treated objects.
  • Each JS object has an inheritance chain up to the general Object.prototype
  • JS Engines in background use object shapes for object prototypes
  • Prototypes also use Inline Cache to have quick access over Shape chain
  • ValidityCell guarantee that cached prototype didn’t change
  • Once ValidityCell became invalid inline caching is not used anymore

JS inline caching - validity cell

Classes

Classes are syntax for the prototype.

Garbage Collection

Garbage collection

  • Garbage Collector - the mechanism for real-time deletion of unused dynamic memory
  • Stop a main thread to rearrange references
  • Responsible for memory optimization
  • Usually use Mark & Sweep or derivatives
    • Marking - Mark all alive (which has at least one reference) objects
    • Sweeping - Free memory from all objects left
  • Usually, objects are divided into generations:
    • Young - Most instances die young, so over the young generation, GC runs more times
    • Old - Old objects live much longer e.g. global objects
  • Optimally GC run when idle time detected e.g. on watching a video, reading longer text

Memory Leak - Accidental Global Variables

When variable inside a function is defined without var prefix it cause that object is assigned to  window

function foo(arg) {
    bar = "hidden global variable";
}

If this was used it may also cause the same issue:

function foo() {
    this.variable = "potential accidental global";
}
foo();

To avoid it 'use strict'; the clause can be added at the beginning of JS file. To avoid it 'use strict'; the clause can be added at the beginning of JS file.

Why JS is not server side compiled ?

You can ask why not to send compiled/bytecode to the browser. This could solve the problem with code optimization. To get the answer we need to understand for what purpose JS was designed and what are pros of this solution.

Simple glue language

The idea of JS was to be the simple scripting language for non-developers. JS had to be like glue between Java applets.

We aimed to provide a “glue language” for the Web designers and part time programmers who were building Web content from components such as images, plugins, and Java applets. We saw Java as the “component language” used by higher-priced programmers, where the glue programmers – the Web page designers – would assemble components and automate their interactions using JS.

In this sense, JS was analogous to Visual Basic, and Java to C++, in Microsoft’s programming language family used on Windows and in its applications. This division of labor across the programming pyramid fosters greater innovation than alternatives that require all programmers to use the “real” programming language (Java or C++) instead of the “little” scripting language.

Brendan Eich JS Initial Designer - https://a-z.readthedocs.io/en/latest/javascript.html

Embedding into HTML

JavaScript was designed to be embedded directly in HTML, like <input type="button" onclick="alert('hello world')">. This requires JS to be in a textual format.

Easier development

We don’t need any compiler to JS development only text editor and browser are needed.

Standardization problems

Each browser implements EcmaScript in its own way, the only high-level specification defined by committees is agreed. There is no standardization for bytecode or more optimized code.

Minified and Compressed code

As EcmaScript can be compressed and minified quite well it’s not big loose to not compile on server side.

Performance advice

  • Always initialize objects in the same way
  • Instead of delete property, prefer to use null or undefined
  • Don’t mix object shapes in an array
  • Keep order on object creation - Order in which the variables are defined is important
  • Don’t change prototypes if possible (or change as early as possible)
  • Avoid the allocation of global objects

Sources