This is the second article in a series.

The object Model is used to optimize access to properties of objects.

Property review

The ECMA specification defines all objects as string keys mapped to a dictionary of property Attributes.

Shapes

When different objects have the same property keys, we say they have the same shape. The JS engine uses shape to optimize access to the property. If we save each object as a dictionary, there are two problems:

  1. How to get property attributes efficiently
  2. If the object of the same shape is saved in full, the information is redundant

Therefore, the JS engine stores the shape separately, which contains the attribute key and attributes other than [[value]], and the offset field to indicate its position in the corresponding object. This solves both of these problems

Transition chains and trees

When we add new properties to an existing Shape, the new Shape stores the new properties and uses Transition Chains to connect to the old Shape. Each shape can be joined by multiple new shapes to form a tree.

An initial Shape is empty when the object starts with no attributes.

But when an object has a lot of properties, the complexity becomes O (n) when looking for a property along transition Chains, or it degenerates into a dictionary.

Inline Caches (ICs)

IC is used to cache the offset corresponding to the shape specified property.

Validity cells

The previous is mainly about the attributes of the object itself, and JS inheritance is based on the prototype, and [[proto]] is also a common attribute, so when we read the attributes on the prototype, we have to query along the prototype chain. Such as

class Bar {
constructor(x) { this.x = x; }
getX() { return this.x; }
}
const foo = new Bar(true);
const x = foo.getX();
Copy the code

Three steps are required to read getX

  1. Check whether getX is in itself
  2. Look for the prototype
  3. Determine if getX is in the prototype

That is, the total lookup is 1+2N, where N is the number of prototypes on the prototype chain. If we save the shape of the prototype instead of the prototype itself, the complexity is reduced to 1+N and inline cache is added on top of that.

The Shape of each stereotype is considered unique and not shared with other objects, and a ValidityCell is associated to indicate whether the stereotype object itself or the upper part of the stereotype chain is modified

This means that if Object.prototype is modified, all caches are invalidated, and modifying the prototype chain itself is bad behavior unless it has been modified before other code has been executed

On the next lookup, if the cache hits and ValidityCell is valid, it can read directly from the offset specified by the corresponding Shape.

Array related

Where array is a special object with an automatically updated length attribute. The index is a number in the valid range 0 to 2³²−2, and the corresponding element is represented by the corresponding string key, for example, the 0th attribute key is ‘0’. The attribute of each index attribute defaults to writable, Enumerable, and signals, so the attribute information is not saved and is different from any other named attribute. If you manually modify an attribute for an element, the JS engine will need to save the element using a dictionary, so don’t do that.

reference

  • JavaScript engine fundamentals: Shapes and Inline Caches
  • JavaScript engine fundamentals: optimizing prototypes