Hidden classes & object shapes
Learn how JavaScript engines use hidden classes, transition trees, descriptor arrays, fast properties, and migrations to speed up objects.
- 01Explain an object's planSay what a hidden class (a map in V8) holds, and why objects built the same way can share one, so the engine reads values from fixed slots.
- 02Follow maps and storageTrace how adding properties moves an object from map to map, and tell apart in-object slots, the extra list beside an object, and dictionary mode.
- 03Keep V8 facts apart from language rulesPredict what
delete,Object.freeze, slack tracking, and a decimal value do in V8 12.4, and know which of these your code can actually see.
Objects can change, but they still get a fixed plan
In JavaScript you can add a property to an object at any time. You can also change it, delete it, or freeze the whole object. That makes objects very flexible.
The engine is the program that runs your JavaScript. Chrome and Node use an engine called V8. The engine wants to read a property like user.name fast. But if any object could hold any names in any order, the engine would have to search the object for name on every read. Searching is slow.
A hidden class is the engine's plan for an object. It is "hidden" because your code can't see it. The plan says three things:
- which property names the object has,
- the order they were added in,
- which numbered slot holds each value.
Objects built the same way share one plan. So the engine can read a value straight from its slot, without a search.
Each engine has its own word for this plan. V8 calls it a map. Firefox's engine, SpiderMonkey, calls it a shape. Safari's engine, JavaScriptCore, calls it a structure. They all do the same job. This lesson uses V8's word, map.
The Inline caches lesson showed how one line of code remembers the map it saw last time, so the next read only needs a quick check. This lesson looks inside that map.
Two kinds of facts appear in this lesson. Keep them apart:
- Language rules are what your code can see, like property order,
delete, and freezing. They are the same in every engine. - Engine facts are how V8 stores objects inside. Your code can't see them, and they can change between versions. The lesson checks them in Node 22, which uses V8 12.4.
Objects built the same way share a map
Normal JavaScript can't show you a map. But it can show one clue: the order of property names. Object.keys(obj) returns an object's property names, in the order they were added.
function makeTicket(name) { return { name, platform: 2, paid: true };} const asha = makeTicket("Asha");const ravi = makeTicket("Ravi");const meera = { platform: 2, name: "Meera", paid: true }; console.log(Object.keys(asha).join(","));console.log(Object.keys(ravi).join(","));console.log(Object.keys(meera).join(","));- Lines 1 to 3 make a function,
makeTicket. It always builds a ticket withname, thenplatform, thenpaid. - Lines 5 and 6 build Asha's and Ravi's tickets with it.
- Line 7 writes Meera's ticket by hand, with
platformfirst. - Lines 9 to 11 print
name,platform,paidtwice, thenplatform,name,paid.
asha and ravi were made by the same function, so they share one map. Each object keeps only its values, and the map says which slot holds which name.Asha's and Ravi's tickets were built the same way, so V8 gives them one map. Meera's ticket has the same names in a different order, so it gets a different map. Normal code can't check this. The next code can.
V8 has special test functions whose names start with %. For example, %HaveSameMap(a, b) returns true when two objects share one map. These functions only work when you start Node with node --allow-natives-syntax. They are not part of JavaScript, so never use them in a real app.
function Point(x) { this.x = x;} const a = { x: 1, y: 2 };const b = { x: 3, y: 4 };const c = { y: 2, x: 1 };const p = new Point(1);const q = new Point(2); console.log("node", process.version);console.log("v8", process.versions.v8);console.log("same order", %HaveSameMap(a, b));console.log("different order", %HaveSameMap(a, c));console.log("constructor start", %HaveSameMap(p, q));p.y = 2;console.log("after one transition", %HaveSameMap(p, q));q.y = 3;console.log("after both transition", %HaveSameMap(p, q));- Lines 11 and 12 print the Node and V8 versions.
- Lines 5 and 6 add
x, theny. Line 13 printssame order true. - Line 7 adds
yfirst. Line 14 printsdifferent order false. - Lines 8 and 9 build two objects with the
Pointconstructor. A constructor is a function you call withnewto build objects. Line 15 printsconstructor start true. - Line 16 adds
ytoponly. Line 17 printsafter one transition false. - Line 18 adds
ytoqtoo. Line 19 printsafter both transition true.
The lesson test runs this code in Node 22 with V8 12.4. It prints exactly the results above: the same order shares a map, a different order doesn't, and two Point objects split apart when only one gets y, then share a map again once both have it.
The table below lists each engine's word for a map. Read each row as: this engine, its word, and one thing to remember.
| Which engine | What it calls the plan | What to remember |
|---|---|---|
| V8 (Chrome, Node) | A map, also called a hidden class. | Every object points to its map first. V8 compares maps to check quickly that two objects have the same plan. |
| SpiderMonkey (Firefox) | A shape. | It does the same job: it tells property reads and inline caches where each value is. |
| JavaScriptCore (Safari) | A structure. | It does the same job inside Safari's engine. |
You can read more from the engine teams. The V8 maps documentation explains maps and the lists they point to. Fast properties in V8 explains where property values are stored. Mathias Bynens and Benedikt Meurer's Shapes and Inline Caches compares the words different engines use.
Adding a property moves an object to the next map
A new, empty object starts on a first map with no names. The step-through below calls it Root. When you add a property, the object moves to a new map that has one more name. That move is called a transition.
- Add
name, thenscore: the object goes fromRoottoRoot.nametoRoot.name.score. - Add
score, thenname: it goes fromRoottoRoot.scoretoRoot.score.name.
All these moves together form a tree that starts at Root. One path through the tree is a transition chain. Objects that add the same names in the same order follow the same chain, and end on the same map.
At a school, every uniform has its badges in the same places. A teacher looks at the left pocket and finds the name badge. If one student sews the badges on in a different order, the teacher needs a different plan to find them quickly.
- In real life: A plain uniform with no badges
- In JavaScript: The
Rootmap of a new, empty object - In real life: Name badge first, then house badge
- In JavaScript: Adding
name, thenscore - In real life: House badge first, then name badge
- In JavaScript: Adding
scorefirst: a different branch
The step-through is a small model written in plain JavaScript. It isn't V8, but it follows the same rule. Each record keeps its map name as text in record.map. transitions is a JavaScript Map, a list of keys and values. Here it remembers every move made so far, so nextMap reuses a move another record already made.
Follow Asha, Ravi, and Meera as each new property moves them to the next map. This is a small model in plain JavaScript, not V8 itself.
script
const transitions = new Map();function nextMap(current, property) { const key = current + "->" + property; if (!transitions.has(key)) transitions.set(key, current + "." + property); return transitions.get(key);} function makeRecord(label) { return { label, map: "Root", slots: {} };} function store(record, property, value) { record.map = nextMap(record.map, property); record.slots[property] = value; return record;} store(asha, "name", "Asha");store(asha, "score", 10);const ravi = makeRecord("Ravi");store(ravi, "name", "Ravi");store(ravi, "score", 20);const meera = makeRecord("Meera");store(meera, "score", 30);store(meera, "name", "Meera"); console.log(asha.map === ravi.map);console.log(asha.map === meera.map);Lines 19 and 20 move Asha along the chain Root.name, then Root.name.score. Ravi follows the same chain on lines 22 and 23, so line 28 prints true. Meera adds score first on lines 25 and 26, so line 29 prints false.
name first and share a map, but Meera adds score first and ends on another branch.Now build your own tree. Add x, y, or z to a new object in any order, then save it and build another. The map names M0, M1, and so on are numbers the model makes up as it creates each map.
Add x, y, or z to the new object. Then save it, and build another object in the same order or a different one.
Descriptor arrays: the list of names behind a map
A map points to a list with one entry for each property. Each entry is called a descriptor. A descriptor holds:
- the property name,
- its settings, like whether it can be changed (the property descriptors lesson covers these),
- what kind of value it holds, like a small whole number,
- the slot where its value lives.
V8 keeps these entries in a list called a DescriptorArray. Maps on one chain can share one descriptor array. The map for { x } and the map for { x, y } can use the same array. The first map just reads fewer entries: only the first one.
The next code uses another V8 test function, %DebugPrint(obj). It prints V8's own report about an object, including its map and the address of its descriptor array. An address is the place in memory where something is stored.
const one = {};one.x = 1;const two = {};two.x = 1;two.y = 2; console.log("ONE");%DebugPrint(one);console.log("TWO");%DebugPrint(two);- Lines 1 and 2 give
onethe map for{ x }. - Lines 3 to 5 give
twothe map for{ x, y }, one step further along the same chain. - Lines 8 and 10 print V8's report for each object.
The test reads both reports. Both maps name the same DescriptorArray[2] address, which is a list with 2 entries. The address is different on every run, so the test only checks that the two addresses match.
This is why the name "hidden class" can mislead. It isn't a class you write in JavaScript. It is a small record inside the engine, and maps on the same chain share parts of it.
Where property values are stored
V8 keeps two kinds of properties apart:
- Named properties, like
user.name. This section is about these. - Elements, the numbered entries of an array, like
list[0]. The next lesson covers those.
A named property's value can live in one of three places:
- In the object itself ("in-object"). This is the fastest place.
- In an extra list beside the object, called the properties backing store. The map still says which slot holds which name, so this is fast too.
- In a dictionary: the object keeps its own table of names and values, with no shared map to say where things are. This is flexible, but slower to read.
The first two are called fast properties. The dictionary kind is called slow properties, or dictionary mode.
const fast = { name: "Asha", score: 0, active: true };const extended = { name: "Ravi", score: 0, active: true };extended.city = "Pune"; console.log(Object.keys(fast).join(","));console.log(Object.keys(extended).join(","));console.log("city" in extended);- Lines 1 and 2 build two objects with the same three names.
- Line 3 adds
citytoextendedafterwards. - Lines 5 and 6 print the names. Line 7 prints
true, because"city" in extendedchecks whether the property exists.
Nothing in this output says where V8 stored city. That stays hidden.
The table below lists the three places. Read each row as: where the value lives, what that means, and why V8 uses it.
| Where the value lives | What it means | Why V8 uses it |
|---|---|---|
| In the object itself (in-object) | The value sits inside the object, in a fixed slot. | It is the fastest place. But an object can't grow in place forever. |
| In an extra list beside the object (backing store) | The value sits in a separate list. The map still says which slot holds which name. | It is still fast, because the map says exactly where to look. |
| In a dictionary (slow properties) | The object keeps its own table of names and values. | It handles deletes and many new names well, but it is slower to read than fixed slots. |
The next code uses one more V8 test function, %HasFastProperties(obj). It returns true when the object uses fast properties, and false when it uses a dictionary.
const removedMiddle = { a: 1, b: 2, c: 3 };console.log("fast before delete", %HasFastProperties(removedMiddle));delete removedMiddle.b;console.log("fast after delete middle", %HasFastProperties(removedMiddle)); const removedLast = { a: 1, b: 2, c: 3 };delete removedLast.c;console.log("fast after delete last", %HasFastProperties(removedLast)); const many = {};for (let i = 0; i < 80; i += 1) many["p" + i] = i;console.log("fast after many", %HasFastProperties(many)); const frozen = { a: 1, b: 2 };console.log("fast before freeze", %HasFastProperties(frozen));Object.freeze(frozen);console.log("fast after freeze", %HasFastProperties(frozen));console.log("is frozen", Object.isFrozen(frozen));In Node 22 with V8 12.4, the test saw these results:
- A new object is fast (line 2).
- After deleting the middle property, it is not fast (line 4).
- After deleting the last property, it is not fast either (line 8).
- After adding 80 properties in a loop, it is not fast (line 12).
- A frozen object with two properties is still fast (line 17).
Slack tracking trims spare space
When you build objects with a constructor, V8 doesn't know yet how many properties they will end up with. So the first objects get some spare in-object slots, just in case. This spare room is called slack.
After a number of objects have been built, V8 looks at how many slots were really used. Then it trims the spare ones from the map. This is slack tracking. V8's slack tracking post says the counter starts at about seven constructions.
In a new notebook, you leave a few blank lines under each topic in case you need more notes. After a few pages, you know how much space you really use, so you stop leaving blank lines.
- In real life: The first pages leave blank lines
- In JavaScript: The first objects get spare slots
- In real life: Later you only leave the lines you need
- In JavaScript: V8 trims the spare slots from the map
- In real life: Notes you wrote stay where they are
- In JavaScript: Properties keep their slots
- In real life: Extra notes go on a separate page
- In JavaScript: Extra properties go to the backing store
function Seat(name) { this.name = name; this.paid = true;} const first = new Seat("Asha");console.log("FIRST");%DebugPrint(first); for (let i = 0; i < 12; i += 1) new Seat("warm" + i);const later = new Seat("Ravi");console.log("LATER");%DebugPrint(later);console.log("same map after trim", %HaveSameMap(first, later));- Lines 1 to 4 make the
Seatconstructor. It gives every seat two properties. - Line 6 builds the first seat, and line 8 prints V8's report about it.
- Line 10 builds 12 more seats, which ends the warm-up.
- Line 11 builds one more seat, and line 13 prints its report.
- Line 14 checks whether
firstandlatershare a map.
The test compares the two reports. The first seat has more in-object slots, and some of them are unused. The later seat has fewer in-object slots, and 0 unused ones. Line 14 still prints true: both seats share one map, because V8 trimmed that map itself.
Dictionary mode, delete, and freeze
In dictionary mode, an object keeps its own table of names and values, instead of using a shared map's slots. This isn't an error or a punishment. V8 picks it when fixed slots stop being a good fit:
- after you
deletea named property, which leaves a gap in the plan, - after you add many different names to one object, one by one.
Freezing is different. Object.freeze(obj) makes an object read-only: nothing can be added, removed, or changed. V8 moves a frozen object to a new map, but a small frozen object can keep fast properties.
const row = { id: 1, name: "Asha", city: "Pune" };delete row.city; const settings = { theme: "dark", compact: false };Object.freeze(settings); console.log(Object.keys(row).join(","));console.log(Object.isFrozen(settings));console.log(settings.theme);- Line 2 removes
cityfromrow. - Line 5 freezes
settings. - Line 7 prints
id,name. - Line 8 prints
true.Object.isFrozenchecks whether an object is frozen. - Line 9 prints
dark. Freezing keeps the values that were already there.
row uses a shared map. After delete row.city, row keeps its own list of names and values, which is slower to read.const removed = { id: 1, name: "Asha", city: "Pune" };delete removed.city;console.log("delete stays fast", %HasFastProperties(removed)); const settings = { theme: "dark", compact: false };console.log("freeze before", %HasFastProperties(settings));Object.freeze(settings);console.log("freeze after", %HasFastProperties(settings));console.log("same map as unfrozen", %HaveSameMap({ theme: "dark", compact: false }, settings));- Line 3 prints
delete stays fast false: after the delete, the object uses a dictionary. - Lines 6 and 8 print
truebefore and after the freeze: the frozen object is still fast. - Line 9 compares
settingswith a new unfrozen object that has the same names. It printssame map as unfrozen false: freezing movedsettingsto a new map.
The next code prints V8's full report for a deleted object and for a frozen object. The lesson test looks for a few words in them:
- For the deleted object:
dictionary_mapandNameDictionary. ANameDictionaryis V8's table of names and values. - For the frozen object:
non-extensible, which means no new properties can be added, andstable_map.
const dictionary = { a: 1, b: 2, c: 3 };delete dictionary.b;console.log("DICTIONARY");%DebugPrint(dictionary); const frozen = { a: 1, b: 2 };Object.freeze(frozen);console.log("FROZEN");%DebugPrint(frozen);The table below sums up these tests. Read each row as: what you do, what V8 12.4 did, and what the test saw.
| What you do | What V8 12.4 did | What the test saw |
|---|---|---|
delete obj.b, a middle property | The object moved to a dictionary. | %HasFastProperties printed false. |
delete obj.c, the last property | The object moved to a dictionary too. | %HasFastProperties printed false, so don't count on deleting the last property being safe. |
| Add 80 names, one by one | The object moved to a dictionary. | %HasFastProperties printed false. |
Object.freeze(obj) | The object moved to a new, frozen map, but stayed fast. | %HasFastProperties printed true, and %HaveSameMap with an unfrozen copy printed false. |
- A constructor always adds
name, thenscore, thenactive. - Code your app uses a lot deletes
cityfrom a row after building it. Object.freezeis called on a small settings object that already has all its properties.- A plugin object gets 80 different property names while the program runs.
- Two objects are written as
{ x, y }, in the same order. - A finished settings object is frozen before other code uses it.
Sort each object by how V8 12.4 stores it, based on the tests in this lesson.
Old maps get replaced: deprecation and migration
A map also remembers what kind of value each property holds. V8 has two kinds of numbers here:
- a Smi (short for "small integer") is a small whole number, like
1; - a Double is a number with a decimal part, like
1.5.
A Smi is cheaper to store. So a new { x: 1 } gets a map that says "x is a Smi".
Now store 1.5 in x. It doesn't fit. V8 makes a new map that says "x is a Double" and moves the object there. The old map is marked deprecated, which means outdated.
Other objects still on the old map are not moved right away. Each one moves the next time it is used. That move is called migration.
The step-through is a model in plain JavaScript. Each object keeps its map name as text, like "M1:x=Smi".
Follow a model of an outdated map: a decimal moves b to a new map, and a moves the next time it is used.
script
const b = { map: "M1:x=Smi", x: 2 };console.log(a.map === b.map); b.x = 1.5;b.map = "M2:x=Double";const deprecated = "M1:x=Smi";console.log(a.map === b.map); if (a.map === deprecated) { a.x = Number(a.x); a.map = "M2:x=Double";}console.log(a.map === b.map);- Line 3 prints
true:aandbstart onM1. - Line 5 stores
1.5inb.x, and line 6 movesbtoM2. Line 8 printsfalse. - Line 10 notices that
ais still on the outdated map, and line 12 moves it. Line 14 printstrue.
1.5 doesn't fit the old map, so b moves to a new map. Object a moves there too, the next time it is used.The real V8 code below does the same with real objects.
const a = { x: 1 };const b = { x: 2 };console.log("start same", %HaveSameMap(a, b)); b.x = 1.5;console.log("after double same", %HaveSameMap(a, b)); console.log("A BEFORE");%DebugPrint(a);console.log("access", a.x);console.log("A AFTER");%DebugPrint(a);console.log("after access same", %HaveSameMap(a, b));- Lines 1 to 3 build
aandb, and printstart same true. - Line 5 stores
1.5inb.x. Line 6 printsafter double same false. - Line 9 prints V8's report for
abefore it is used. - Line 10 reads
a.x. That read is the use that movesa. - Line 12 prints the report again. Line 13 prints
after access same true.
The first report for a says deprecated_map: its map is outdated. After the read, the report says stable_map and migration_target: a is now on the map that objects move to. %HaveSameMap(a, b) goes from true, to false, and back to true.
Habits that keep objects fast
Most of the time, you don't need to think about maps at all. A few simple habits are enough. The code below shows the first two.
function makeCard(name, city) { return { name, city, selected: false, error: null, };} const first = makeCard("Asha", "Pune");const second = makeCard("Ravi", "Kochi");second.error = "missing phone";first.error = null; console.log(Object.keys(first).join(","));console.log(Object.keys(second).join(","));- Lines 1 to 8 make
makeCard. It builds every card with the same four properties, in the same order. errorstarts asnull, which means "no error yet".- Lines 10 and 11 build two cards. Line 12 sets Ravi's error to a message.
- Line 13 sets Asha's error to
nullinstead of deleting it. - Lines 15 and 16 print the same names for both cards:
name,city,selected,error.
- Build similar objects with one function or constructor, so they get the same properties in the same order.
- To clear a property on an object your code uses a lot, set it to
nullorundefinedinstead of deleting it. - Measure first. Change clear code only when a profiler, a tool that shows where your program spends its time, points at a slow spot.
- For related topics, see inline caches, prototype internals, property descriptors, and freezing and sealing.
Common misconceptions
- "Hidden classes are JavaScript classes." They are plans inside the engine. They have nothing to do with the
classkeyword. - "The same property names always mean the same map." The order matters too. So do each property's settings, the kind of value it holds, and the object's prototype.
- "Deleting the last property keeps an object fast." Not in V8 12.4. The test deleted the middle property and the last one, and both objects stopped being fast.
- "Freezing makes objects slow." Freezing moves the object to a new map, but a small frozen object can keep fast properties.
- "Engine tricks matter more than clear code." Change code for maps only when a measurement shows a slow spot.
The table below lists four ideas that are easy to mix up. Read each row as: the idea, what it means, and what it is not.
| Idea | What it means | What it is not |
|---|---|---|
| Language rules | What your code can see: properties, prototypes, property settings, delete, and freezing. | Not hidden classes. The JavaScript standard never mentions them. |
| Hidden class, map, or shape | The engine's plan for an object: its names, their settings, the kind of value each holds, and their slots. | Not class User {}. Your code can't see it. |
| Inline cache | A small memory at one line of code. It remembers the map it saw last time. | Not the map itself. It only checks the map; the inline caches lesson covers it. |
| Dictionary mode | The object keeps its own table of names and values, instead of fixed slots. | Not a bug in your code. It only matters in code a profiler shows is slow. |
Practice exercises
Read the model code. Type the two values that the last two lines print.
const transitions = new Map();
function nextMap(current, property) {
const key = current + "->" + property;
if (!transitions.has(key)) transitions.set(key, current + "." + property);
return transitions.get(key);
}
function makeRecord(label) {
return { label, map: "Root", slots: {} };
}
function store(record, property, value) {
record.map = nextMap(record.map, property);
record.slots[property] = value;
return record;
}
const asha = makeRecord("Asha");
store(asha, "name", "Asha");
store(asha, "score", 10);
const ravi = makeRecord("Ravi");
store(ravi, "name", "Ravi");
store(ravi, "score", 20);
const meera = makeRecord("Meera");
store(meera, "score", 30);
store(meera, "name", "Meera");
console.log(asha.map === ravi.map);
console.log(asha.map === meera.map);It prints true, then false. Asha and Ravi follow the same chain; Meera ends on a different branch.
What does V8 call the list that holds each property's name, settings, and slot for a map?
DescriptorArray is the list of property entries that a map points to.
What does the lesson's V8 code print after delete stays fast?
It prints false for delete stays fast: after the delete, the object no longer has fast properties.
Run the starter code. In what order does it print the property names?
function makeRow(name) {
return { name, score: 0, active: true };
}
console.log(Object.keys(makeRow("Asha")).join(","));function makeRow(name) {
return { name, score: 0, active: true };
}
console.log(Object.keys(makeRow("Asha")).join(","));Every row gets name, score, and active, in that order, so it prints name,score,active.
Which three values does the migration model print?
const a = { map: "M1:x=Smi", x: 1 };
const b = { map: "M1:x=Smi", x: 2 };
console.log(a.map === b.map);
b.x = 1.5;
b.map = "M2:x=Double";
const deprecated = "M1:x=Smi";
console.log(a.map === b.map);
if (a.map === deprecated) {
a.x = Number(a.x);
a.map = "M2:x=Double";
}
console.log(a.map === b.map);It prints true false true: they share a map, split when b gets a decimal, then share the new map after a moves.
Name one habit you would use for objects in a list that a profiler showed is slow.
Good answers: build objects with the same properties in the same order, avoid delete on objects used a lot, set a property to null instead of deleting it, keep objects built the same way, or measure first.
Check your understanding
For each question, first ask: is this a language rule your code can see, or a V8 fact? For code questions, run the lines in order in your head.
Question 1 of 8What is a hidden class, or map?
Choose an answer to see the explanation.
Question 2 of 8What does this code print about name order?
Read the code, then predictconst a = { x: 1, y: 2 }; const b = { y: 2, x: 1 }; console.log(Object.keys(a).join("|")); console.log(Object.keys(b).join("|"));Choose an answer to see the explanation.
Question 3 of 8Why do maps form a tree?
Choose an answer to see the explanation.
Question 4 of 8Which sentence about descriptor arrays is true?
Choose an answer to see the explanation.
Question 5 of 8What does this freeze code print?
Read the code, then predictconst settings = { theme: "dark" }; Object.freeze(settings); console.log(Object.isFrozen(settings)); console.log(settings.theme);Choose an answer to see the explanation.
Question 6 of 8In the V8 12.4 test, what happened after
deleteremoved a property?Choose an answer to see the explanation.
Question 7 of 8What does this map model print?
Read the code, then predictconst a = { map: "M1:x=Smi", x: 1 }; const b = { map: "M1:x=Smi", x: 2 }; console.log(a.map === b.map); b.x = 1.5; b.map = "M2:x=Double"; const deprecated = "M1:x=Smi"; console.log(a.map === b.map); if (a.map === deprecated) a.map = "M2:x=Double"; console.log(a.map === b.map);Choose an answer to see the explanation.
Question 8 of 8Which habit for real apps follows from this lesson?
Choose an answer to see the explanation.
Key takeaways
- A hidden class is the engine's plan for an object. V8 calls it a map, SpiderMonkey a shape, and JavaScriptCore a structure.
- Adding a property moves an object to the next map. The same names in a different order end on a different map.
- A map points to a descriptor array, and maps on one chain can share it.
- Fast properties use fixed slots. Dictionary mode, used after a
deleteor many new names, uses the object's own table. - Slack tracking trims spare slots from early objects. A value that doesn't fit, like a decimal, can replace a map, and old objects move later.
- Build similar objects the same way, avoid
deleteon objects you use a lot, and measure before you optimize.
Remember the one-liner.
A hidden class is the engine's shared plan for objects that were built the same way.
You came from Symbols inside the engine. Up next: Fast arrays & elements kinds, where arrays get their own plans and transitions.