docs/objects/descriptor-and-transition-arrays.md
To save memory and enable fast property access, V8 shares structural information between objects. This is achieved using DescriptorArray and TransitionArray, which are linked from an object's Map.
A DescriptorArray holds the property descriptors for a Map. It describes the names, attributes, and locations of properties.
DescriptorArray is a custom array-like structure (inheriting from HeapObject). It does not use a standard FixedArray for its entries to optimize for performance and memory.
Each entry in the array consists of 3 fields, making the entry size 3:
Name).Smi. This includes property attributes (e.g., ReadOnly, DontEnum), kind (Data or Accessor), and location (Field or Descriptor). It also contains a pointer used for sorting.FieldType (wrapped as a MaybeObject) if the property is stored in a field.Instead of every Map having its own DescriptorArray, maps that are part of the same transition tree share a single DescriptorArray.
Map only "sees" a prefix of the shared DescriptorArray. The number of properties valid for a specific map is determined by its NumberOfOwnDescriptors().Map A has property x. NumberOfOwnDescriptors() = 1.Map B (transitioned from A) adds property y. NumberOfOwnDescriptors() = 2.Map A and Map B point to the same DescriptorArray containing [x, y]. Map A only looks at the first element, while Map B looks at both.owns_descriptors)To manage memory and avoid conflicting modifications, only one Map in a sharing group "owns" the DescriptorArray. This is tracked by the owns_descriptors bit in the Map.
If a map needs to be modified in a way that doesn't fit the linear transition model (e.g., an elements kind transition or a prototype transition), V8 may need to split the descriptor array:
DescriptorArray up to the map's NumberOfOwnDescriptors().A TransitionArray holds transitions from one Map to another, forming the edges of the transition tree.
When a property is added or attributes change, V8 searches the current map's TransitionArray for a matching transition. If found, the object takes the target map. If not, a new map is created and a new transition is inserted.
TransitionArray is a WeakFixedArray.
[0]: Pointer to prototype transitions (or Smi 0).[1]: Pointer to side-step transitions (or Smi 0).[2]: Number of transitions (as a Smi).[Key, TargetMap]. The TargetMap is a weak reference, allowing the GC to collect maps that are no longer reachable, automatically pruning the transition tree.Side-step transitions are special edges in the transition tree used for operations that bypass standard property addition, such as Object.assign() or object cloning. They allow V8 to quickly find the target map for these operations if they have been performed before.
Not all maps are connected to a transition tree. A map can be detached.
A map is considered detached if it is not reachable via transitions from a root map.
In V8, Map::IsDetached() returns true if:
JS_OBJECT_TYPE map that has properties (NumberOfOwnDescriptors() > 0) but its back pointer is Undefined.var a = {};
a.x = 1;
var b = {};
b.x = 1;
b.y = 2;
var c = {};
c.x = 1;
c.z = 3;
digraph G {
rankdir=TB;
newrank=true;
node [shape=record];
edge [];
subgraph Objects {
a [label="{a | 1}"];
b [label="{b | 1 | 2}"];
c [label="{c | 1 | 3}"];
}
subgraph Maps {
RootMap [label="Root Map\n(owns=false)"];
MapX [label="Map (x)\n(owns=false)"];
{
rank="same"
MapY [label="Map (x,y)\n(owns=true)"];
MapZ [label="Map (x,z)\n(owns=true)"];
}
DescsXY [label="{DescriptorArray | x | y}"];
DescsXZ [label="{DescriptorArray | x | z}"];
RootMap -> MapX [label="Add 'x'", style=dashed];
MapX -> MapY [label="Add 'y'", style=dashed];
RootMap -> DescsXY;
MapX -> DescsXY;
MapY -> DescsXY;
MapX -> MapZ [label="Add 'z'", style=dashed];
MapZ -> DescsXZ;
}
a->MapX;
b->MapY;
c->MapZ;
}