• Alligator. IO /js/ v8-Engin
  • Translation: Ma Xueqin

V8 is Google’s engine for compiling JavaScript. Firefox also has one, called SpiderMonkey, which is a little different from V8, but very similar overall. We will discuss V8 in this article.

Some basics of the V8 engine:

  • Implemented in C++, in Chrome and node.js (and the latest version of Microsoft Edge)
  • Comply with ECMA-262 standard

The journey of JavaScript

What happens when we parse compressed, obfuscated, and mangled JavaScript in a V8 engine?

The following figure illustrates the process, and we’ll explain each step in detail:

In this article, we’ll look at how JavaScript code is parsed and how to maximize JavaScript compilation efficiency. V8’s optimized compiler (aka Turbofan) takes JavaScript code and turns it into efficient machine code, so the more code we can feed into it, the faster our apps will be. As a side note, the interpreter in Chrome is called Ignition.

JavaScript parsing

The first step in the process is parsing the JavaScript. First, explore what parsing is.

Parsing has two stages:

  • Eager (full resolution) – Parses all code at once
  • Lazy (pre-parsing) – Do as little parsing as needed and save the rest for later

Which way is better depends on the actual situation.

Let’s look at a piece of code.

// Variable declarations are parsed immediately const a = 1; const b = 2; // Do not parse for nowfunction add(a, b) {
  returna + b; } // the add method was executed, so we need to return to parse the method add(a, b);Copy the code

Variable declarations are parsed immediately and functions are parsed lazily, but adding (a, b) immediately after the above code indicates that the add method is needed immediately, so in this case it is more efficient to parse the add function on the fly.

In order for the add method to be resolved immediately, we can do this:

// Variable declarations are parsed immediately const a = 1; const b = 2; // eager parse this too var add = (function(a, b) {
  returna + b; }) (); // The add method has already been parsed, so this code can immediately execute add(a, b);Copy the code

This is how most modules are created. So, is immediate parsing the best way to use efficient JavaScript?

We can use the tooloptimize-js to completely immediately resolve common library code, such as the well-known Lodash, and the optimization is remarkable:

  • Not using optimize-JS: 11.86ms
  • Optimize-js was used: 11.24ms

It is important to note that this result is obtained in Chrome and results in other environments are not guaranteed:

If you need to optimize your application, you must test it in all environments.

Another parse-related tip is not to have functions nested:

// Bad wayfunctionSumOfSquares (a, b) {// This will be repeated lazy-parsedfunction square(num) {
    return num * num;
  }

  return square(a) + square(b);
}
Copy the code

The improved method is as follows:

function square(num) {
  returnnum * num; } // Good wayfunction sumOfSquares(a, b) {
  return square(a) + square(b);
}

sumOfSquares(a, b);
Copy the code

In the example above, the Square method was lazily parsed only once.

Inline function

Chrome sometimes overwrites JavaScript code, such as inline functions.

Here is a code example:

const square = (x) => { return x * x }

const callFunction100Times = (func) => {
  for(leti = 100; i < 100; I++) {// the func argument will be called 100 times func(2)}} callFunction100Times(square)Copy the code

The above code is optimized for V8 as follows:

const square = (x) => { return x * x }

const callFunction100Times = (func) => {
  for(leti = 100; i < 100; I++) {// the function will not be called continuously after being inlinedreturn x * x
  }
}

callFunction100Times(square)
Copy the code

As you can see above, V8 actually concatenates the square function to eliminate the step of calling the function. This is useful for improving the performance of your code.

Inline function problem

There is a bit of a problem with this method, so let’s look at this code:

const square = (x) => { return x * x }
const cube = (x) => { return x * x * x }

const callFunction100Times = (func) => {
  for(leti = 100; i < 100; Func (2)}} callFunction100Times(square) callFunction100Times(cube)Copy the code

The above code calls the square function 100 times, followed by the Cube function 100 times. Before calling Cube, we must first de-optimize callFunction100Times, since we have already inlined the Square function. In this example, the square function seems to be faster than the Cube function, but in reality, the entire execution process is longer because of this de-optimization step.

object

When it comes to objects, V8 has a type system underneath that distinguishes them:

singleton

Objects have the same keys; there is no difference between these keys.

// singleton example const person = {name:'John' }
const person2 = { name: 'Paul' }
Copy the code

polymorphism

Objects have similar structures, with some subtle differences.

// Polymorphic example const person = {name:'John' }
const person2 = { name: 'Paul', age: 27 }
Copy the code

Complex state

The two objects are completely different and cannot be compared.

// const person = {name:'John' }
const building = { rooms: ['cafe'.'meeting room A'.'meeting room B'], doors: 27 }
Copy the code

Now that we know about the different objects in V8, let’s look at how the V8 engine optimizes objects.

Hidden classes

Hidden classes are V8’s way of distinguishing objects.

Let’s break down the process.

First declare an object:

const obj = { name: 'John'}
Copy the code

V8 will declare a classId for this object.

const objClassId = ['name', 1]
Copy the code

The object is then created as follows:

const obj = {... objClassId,'John'}
Copy the code

Then when we get the name property in the object:

obj.name
Copy the code

V8 does the following lookup:

obj[getProp(obj[0], name)]
Copy the code

That’s how V8 creates objects, and let’s see how to optimize them and reuse classId.

Suggestions for creating objects

Properties should be declared in constructors as much as possible to keep the structure of the object unchanged, allowing V8 to optimize the object.

class Point { constructor(x,y) { this.x = x this.y = y } } const p1 = new Point(11, Const p2 = new Point(33, 44) // Hidden classId created const p2 = new Point(33, 44)Copy the code

The order of attributes should be kept constant, as in the following example:

Const obj = {a: 1} // The hidden classId was created Obj = {a: 1} const obj = {a: 1} const obj = {a: 1} const obj2 = {a: 1} Obj2.b = 3Copy the code

Other optimization suggestions

Let’s take a look at some other JavaScript code optimization tips.

Modify function parameter types

When passing parameters into a function, it is important to ensure that the parameters are of the same type. Turbofan will give up after four attempts at optimization if the parameter type is different.

Here’s an example:

function add(x,y) {
  returnX + y} add(1,2)'a'.'b'// Add ()true.false) the add ({}, {}) add ([], []) / / complex state - in this phase, have tried to 4 + times, won't do optimizationCopy the code

Another suggestion is to ensure that classes are declared in global scope:

// Don't do thatfunctionCreatePoint (x,y) {class Point {constructor(x,y) {this.x = x this.y = y}} // Create a new Point object each timereturn new Point(x,y)
}

function length(point) {
  //...
}
Copy the code

conclusion

Hopefully, you learned a little bit about V8’s basics and how to write better JavaScript code.