Object.create & null-prototype objects
Create objects with a chosen prototype, inspect and change prototype links carefully, and use null-prototype dictionaries without inherited surprises.
- 01Make an object with a chosen prototypeUse
Object.create(proto, descriptors)and check the link it makes. - 02Pick a safe dictionaryChoose between
{},Object.create(null), andMapwhen keys come from users. - 03Keep prototype links stableExplain why
setPrototypeOfis for rare fixes, not for code that runs often.
Choose the prototype link up front
In the last few lessons, you saw that an object can have a prototype. A prototype is a second object that JavaScript checks when the first object doesn't have a property. This lesson shows how to pick that prototype yourself.
Object.create(proto) makes a new, empty object whose prototype is proto. It doesn't call a constructor function. It doesn't copy any properties from proto into the new object. It only adds a link.
Three tools, three jobs:
Object.create(proto)makes a new object linked toproto.Object.create(null)makes a new object with no prototype, so it inherits nothing.Object.setPrototypeOf(obj, proto)changes the link of an object that already exists. Use it rarely, because it makes code slower.
A shop sells the items on its own shelves. When a shelf is empty, the staff ask the warehouse. If the warehouse doesn't have the item either, they ask the warehouse's own supplier, and so on. A new shop picks its warehouse before it opens. It can switch to another warehouse later, but switching on a busy day causes trouble for everyone.
- In real life: A new shop picks its warehouse before it opens
- In JavaScript:
Object.create(proto)picks the prototype when the object is made - In real life: When a shelf is empty, staff fetch the item from the warehouse
- In JavaScript: When a property is missing, JavaScript looks for it on the prototype
- In real life: Switching warehouses on a busy day slows everyone down
- In JavaScript:
Object.setPrototypeOfchanges the link later and slows the code down
JavaScript looks up a property in the same order. First it checks the object itself. Then it checks the prototype, then the prototype's prototype, and so on, until the chain of links ends.
Sometimes you want the opposite: an object with no prototype at all. This is called a null-prototype object. It holds only the keys you put in it. It has no inherited extras such as toString, constructor, or hasOwnProperty. That makes it a safe dictionary: an object used as a lookup table from keys to values. This ties in with Map & Set, Choosing a data structure, and JSON.
Object.create: make an object with a chosen prototype
STEP THROUGHObject.create needs one argument: the object to use as the prototype, or null for none. It returns a new object. The new object keeps a hidden link to that prototype. The specification calls this link [[Prototype]]. The double square brackets mean your code can't read it as a normal property.
const animal = { eats: true };
const rabbit = Object.create(animal);
console.log(rabbit.eats);
console.log(Object.hasOwn(rabbit, "eats"));Line by line:
- Line 1 makes
animal, which ownseats. - Line 2 makes
rabbitand links it toanimal. Nothing is copied. - Line 3 prints
true.rabbithas noeatsof its own, so JavaScript follows the link and finds it onanimal. - Line 4 prints
false.Object.hasOwn(obj, key)asks, "Doesobjitself hold this key?" It ignores the prototype, andrabbitholds nothing.
rabbit starts empty. It only has a link to animal, so rabbit.eats is found on animal and gives true.Object.create also takes an optional second argument: the properties to add to the new object. Each property is described by a property descriptor. A descriptor is a small object that says what the property holds and what you may do with it. Object.defineProperties uses the same format. A descriptor can have these parts:
value: the value the property holds.writable: can you change the value later?enumerable: does the property show up in lists, such as the oneObject.keysgives?configurable: can you delete the property or change its parts later?
The last three are yes-or-no flags. Any flag you leave out is false. This surprises many people.
The step-through below uses a bigger example. Its first line, "use strict", turns on strict mode: a set of stricter rules. In strict mode, some mistakes that would fail quietly throw an error instead. Line 9 sets enumerable: true, so name shows up in Object.keys. It leaves writable out, so at first name can't be changed.
Guess the method's result, the two link checks, and whether line 16 can change name. Then step through the run.
script
"use strict"; greet() { return "hi from base"; },}; const child = Object.create(base, { name: { value: "Kid", enumerable: true },}); console.log(child.greet());console.log(Object.getPrototypeOf(child) === base);console.log(base.isPrototypeOf(child));try { child.name = "Ada";} catch (error) { console.log(error.name);}console.log(child.name);What each part of the run shows:
- Line 8 makes
child, already linked tobase. Line 9 gives it its ownnameproperty. - Line 12 calls
child.greet().childdoesn't owngreet, so JavaScript follows the link and runs the method frombase. It printshi from base. So the method was not copied. - Line 13 checks the link from the child's side.
Object.getPrototypeOf(child)returns the prototype ofchild. That isbase, so it printstrue. - Line 14 checks the link from the other side.
base.isPrototypeOf(child)asks, "Isbaseanywhere in the chain of prototypes behindchild?" It printstrue. - Line 16 tries to change
name.writablewas left out, so it isfalse. In strict mode, the change throws aTypeError, and line 18 prints the error's name. Line 20 printsKid: the value never changed.
Pick the writable descriptor and step again. Now line 16 changes name, and line 20 prints Ada.
const base = { label: "shared" };
const child = Object.create(base, {
name: { value: "Asha", enumerable: true },
});
const descriptor = Object.getOwnPropertyDescriptor(child, "name");
console.log(descriptor.value);
console.log(descriptor.writable);
console.log(descriptor.enumerable);
console.log(descriptor.configurable);Object.getOwnPropertyDescriptor(obj, key) gives back the descriptor of one property that obj owns. Use it to see the flags instead of guessing:
- Line 5 reads the descriptor of
name. - Line 6 prints the value,
Asha. - Line 7 prints
false.writablewas left out, so it isfalse. - Line 8 prints
true. Line 3 setenumerable: true. - Line 9 prints
false.configurablewas left out too.
If you want to change or delete a property later, set all three flags yourself.
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.eats = false;
console.log(rabbit.eats);
console.log(animal.eats);
console.log(Object.hasOwn(rabbit, "eats"));Line 3 writes to rabbit.eats. Writing a property doesn't change the prototype. Instead, JavaScript adds a new eats property to rabbit itself. This own eats now hides the one on animal. Hiding an inherited property like this is called shadowing. The output:
- Line 4 prints
false: the value onrabbit. - Line 5 prints
true:animaldidn't change. - Line 6 prints
true:rabbitnow ownseats.
rabbit.eats puts a new card in rabbit. The card in animal doesn't change, so animal.eats is still true.const profile = Object.create(null, {
name: { value: "Asha" },
});
console.log(Object.keys(profile));
profile.name = "Mina";
console.log(profile.name);The descriptor on line 2 sets only a value, so all three flags are false. This code has no "use strict" line, so it runs with the older, looser rules, often called sloppy mode:
- Line 4 prints
[], an empty list.Object.keyslists only enumerable properties, andnameis not enumerable. - Line 5 tries to change
name. It is not writable, so nothing happens, and no error is shown. - Line 6 prints
Asha. The value didn't change.
In strict mode, line 5 would throw a TypeError instead, as the step-through above showed.
__proto__ inside an object literalAn object literal is an object written with curly braces, like { own: 1 }. Inside one, { __proto__: proto, own: 1 } is special syntax. It makes proto the prototype of the new object while the object is made.
Writing obj["__proto__"] = proto later is different. On a normal object, that write runs an old built-in setter: a hidden function that runs when you assign a property. That setter changes the prototype after the object already exists.
getPrototypeOf & setPrototypeOf
INTERACTIVEThree built-in functions read or change the link of an object that already exists:
Object.getPrototypeOf(obj)returns the prototype ofobj. This is the standard way to look at the link.Object.setPrototypeOf(obj, proto)makesprotothe new prototype ofobj, then returnsobj. If the change isn't allowed, it throws an error.Reflect.setPrototypeOf(obj, proto)makes the same change. Instead of throwing, it returnstrueif the change worked andfalseif it didn't.Reflectis a built-in object that holds these lower-level versions of object operations.
Some links are not allowed:
- You can set the prototype to
null. The chain then ends at that object. - You can't make a loop.
Object.setPrototypeOf(a, a)would makeaits own prototype, so it throws aTypeError. In Node and Chrome the message isCyclic __proto__ value. "Cyclic" means "going round in a circle". - Some objects don't allow a new prototype. A frozen object, made with
Object.freeze, is locked against changes.Reflect.setPrototypeOfreturnsfalsefor it.
const toolbox = { label: "toolbox", find() { return "from toolbox"; } };const archive = { label: "archive", find() { return "from archive"; } };const item = { name: "receipt" }; console.log(item.find?.());Object.setPrototypeOf(item, toolbox);console.log(item.find());console.log(Object.getPrototypeOf(item).label);console.log(Reflect.setPrototypeOf(Object.freeze({}), archive));try { Object.setPrototypeOf(item, item);} catch (error) { console.log(error.name + ": " + error.message);}Press Run to run the code.
Read the code first. It changes the prototype of item after item already exists, so you can see how the tools work. The lesson then explains why you should pick the link when you make the object.
item to toolbox. Now item.find() finds the method on toolbox. It works, but changing a link later makes code slower.Why use it rarely? The JavaScript engine is the program inside the browser (or Node) that runs your code. It learns where each object keeps its properties, so it can find them fast. Changing an object's prototype later throws away what it learned. Code that uses that object can then run slower.
Picture a shop that switches to a new warehouse in the middle of the lunch rush. It can be done in an emergency. But nobody would plan to do it every day. Treat setPrototypeOf the same way: know that it exists, but set the link when you make the object.
- In real life: Staff have learned where everything is in the old warehouse
- In JavaScript: The engine has learned how to find this object's properties fast
- In real life: A new warehouse means everyone has to learn again
- In JavaScript: A new
[[Prototype]]link makes the engine forget what it learned - In real life: Picking the warehouse before opening causes no trouble
- In JavaScript: Classes, constructors, and
Object.createset the link when the object is made
Object.create(null) dictionaries
INTERACTIVEA normal object, such as {}, has Object.prototype as its prototype. That is usually helpful. It gives every object methods such as toString and hasOwnProperty.
But sometimes you use an object as a dictionary, and the keys come from data you don't control, such as words a user types. Then the inherited names get in the way. Even before you add anything:
"toString" in {}is alreadytrue. Theinoperator checks the object and its prototypes.- Reading
({}).toStringgives a function, notundefined.
Object.create(null) makes an object with no prototype. So it has none of these inherited names: no constructor, no toString, and no hasOwnProperty. A key such as "constructor" or "__proto__" is just a normal key there.
{} still finds toString through its prototype. An object made with Object.create(null) has no prototype, so it holds only the keys you add.You can still turn it into JSON. JSON.stringify reads only the object's own enumerable properties, so it doesn't need Object.prototype.
const dict = Object.create(null);
dict.apple = 2;
dict.constructor = 1;
console.log(typeof dict.toString);
console.log(typeof dict.hasOwnProperty);
console.log(Object.hasOwn(dict, "constructor"));
console.log(Object.entries(dict));Line by line:
- Line 1 makes the dictionary, with no prototype.
- Lines 2 and 3 add two keys,
appleandconstructor. - Lines 4 and 5 print
undefined.typeofgives the type of a value as text. Neither method exists here, because nothing is inherited. - Line 6 prints
true.Object.hasOwnis a function onObjectitself, not a method of the dictionary, so it still works. - Line 7 prints the two key-value pairs.
Object.entriesis also a function onObject.
So, to look inside a null-prototype dictionary, use functions on Object, such as Object.hasOwn, Object.keys, and Object.entries.
const keys = ["toString", "__proto__", "constructor", "hasOwnProperty", "apple"]; const plain = {};const dict = Object.create(null);const map = new Map(); for (const key of keys) { const value = key === "__proto__" ? { polluted: true } : "value:" + key; plain[key] = value; dict[key] = value; map.set(key, value);} console.log(typeof ({}).toString);console.log(plain.polluted);console.log(Object.hasOwn(plain, "__proto__"));console.log(dict["__proto__"]);console.log(typeof Object.create(null).hasOwnProperty);console.log(JSON.stringify(dict));prototype changed; polluted=true__proto__?false[object Object]hasOwnPropertyundefinedvalue:__proto__5Before anything is added, ({}).toString is a function. JSON.stringify still works on the null-prototype dictionary: {"toString":"value:toString","__proto__":{"polluted":true},"constructor":"value:constructor","hasOwnProperty":"value:hasOwnProperty","apple":"value:apple"}
On a plain {}, writing __proto__ runs the inherited setter, which changes this object's prototype. On a null-prototype object and on a Map, __proto__ is just a key.
The table compares three ways to store keys and values. Each row is one choice: when it fits best, and what to watch out for.
| What you use | When does it fit best? | What should you watch out for? |
|---|---|---|
A plain object, {} | Use it for records whose field names you know, like user.name or options.theme. | It inherits names like toString, and writing a __proto__ key changes its prototype. |
Object.create(null) | Use it for text keys that come from data or from users. | It has no methods, so use Object.hasOwn(dict, key) and Object.entries(dict). |
Map | Use it when you add and delete keys often, or when keys aren't text. | JSON.stringify doesn't turn it into JSON. Convert it first, for example with Array.from(map). |
In the Choosing a data structure lesson, Object.groupBy returned a null-prototype object. That was on purpose. The group names come from data, and data might contain the word "constructor". A later lesson, Prototype pollution, covers the security side. For now, keep one habit: don't let keys from data turn into inherited behavior.
Prototype performance: keep links stable
This lesson doesn't give speed numbers. A small speed test, called a benchmark, often measures how the engine warms up, not how your real page behaves. But the advice is clear:
- MDN and the teams who build engines say that changing a prototype after an object is made is slow. That covers both
Object.setPrototypeOfand assigning to__proto__. - Long prototype chains also cost time. Each missing property can mean one more link to check.
- Pick the prototype when you make the object: with a class, a constructor function,
__proto__inside an object literal, orObject.create. - Keep chains short and simple for objects your code uses very often.
- Don't use
setPrototypeOfas a normal way to change how an object behaves. - Measure real pages before you try to make them faster. Two later lessons, Hidden classes & object shapes and Prototypes inside the engine, explain what the engine does.
- A fixed settings object with keys your code picks, like
themeandpageSize - A word-count table whose keys come from article text
- Data stored for each page element, with the element object as the key
- A list of user choices you will add, delete, and loop over often
- A JSON response from your API with known fields
- A result like the one
Object.groupBy(items, callback)returns
Put each situation under the best first choice. Then read the explanation for each one.
Where you’ll use this
Most everyday code uses object literals, classes, and Map. You reach for Object.create when the link itself is what you want. For example:
- an object that hands some of its work to another object,
- a stand-in object for tests that gets its default behavior from a real one,
- small parts inside libraries and frameworks,
- dictionaries with no inherited keys.
class User {
greet() { return "hello " + this.name; }
}
function UserRecord(name) {
this.name = name;
}
UserRecord.prototype.greet = function () {
return "hello " + this.name;
};
function makeUser(name) {
return Object.create(UserRecord.prototype, {
name: { value: name, writable: true, enumerable: true, configurable: true },
});
}
const classUser = new User();
classUser.name = "Asha";
const constructorUser = new UserRecord("Asha");
const linkedUser = makeUser("Asha");
console.log(classUser.greet());
console.log(constructorUser.greet());
console.log(linkedUser.greet());
console.log(Object.getPrototypeOf(linkedUser) === UserRecord.prototype);Three objects, one result:
- Lines 1 to 3: the class
UserputsgreetonUser.prototype. Line 15 makesclassUserwithnew User(), so it is linked toUser.prototype. - Lines 4 to 9: the constructor function
UserRecordworks the same way. Line 7 putsgreetonUserRecord.prototypeby hand. Line 17 makesconstructorUserwithnew. - Lines 10 to 14:
makeUsercallsObject.createwithUserRecord.prototypeas the prototype. Line 18 makeslinkedUserthis way, withoutnew. - Lines 19 to 21 each print
hello Asha. Line 22 printstrue:linkedUserhas the same prototype thatnew UserRecord()gives.
Use a class or a constructor when you make many objects of the same kind. Use Object.create when you want to link one object straight to another, or set the property flags yourself.
const animal = { eats: true };
const rabbit = Object.create(animal, {
name: { value: "Bun", writable: true, enumerable: true },
});
const clone = Object.create(
Object.getPrototypeOf(rabbit),
Object.getOwnPropertyDescriptors(rabbit),
);
const spread = { ...rabbit };
console.log(clone.name);
console.log(Object.getPrototypeOf(clone) === animal);
console.log(Object.getOwnPropertyDescriptor(clone, "name").writable);
console.log(Object.getPrototypeOf(spread) === animal);The code makes two copies of rabbit:
- Lines 5 to 8 make
clone.Object.getPrototypeOf(rabbit)gives the same prototype,animal.Object.getOwnPropertyDescriptors(rabbit)gives the descriptors of all the propertiesrabbitowns, flags included. - Line 9 makes
spreadwith{ ...rabbit }. The spread syntax...copies the own enumerable values into a new plain object.
The output:
- Lines 10 to 12 print
Bun,true, andtrue.clonehas the same name, the same prototype, and the same writable flag. - Line 13 prints
false.spreadgot the usualObject.prototype, notanimal.
Both are shallow copies. If a property held another object, both copies would point to that same inner object.
function countWords(words) { const counts = Object.create(null); for (const word of words) { counts[word] = (counts[word] || 0) + 1; } return counts;}console.log(countWords(["constructor", "constructor"]).constructor);The word "constructor" is a well-known trap. Suppose line 2 made a plain {}. Then counts.constructor would already hold an inherited function before any word is counted. So (counts[word] || 0) + 1 on line 4 wouldn't start from 0. It would turn the function into text and stick 1 on the end, so the count becomes strange text instead of a number.
Line 2 uses Object.create(null) instead. The dictionary starts truly empty, with only the keys you add, so line 7 prints 2.
The next lesson, Delegation vs classical inheritance, builds on this. It shows a style called OLOO, short for "objects linked to other objects". OLOO uses Object.create to link objects straight to each other, instead of copying behavior down a family tree of classes.
Common misconceptions
“Object.create copies the prototype’s methods.”
No. It makes a link. When the new object doesn't have a property, JavaScript follows the link to find it.
“A null-prototype object can’t be turned into JSON.”
It can. JSON.stringify reads only the object's own enumerable properties, and a null-prototype object can have those.
“setPrototypeOf is just a harmless assignment.”
It is a standard function, but it is slow. Changing the link makes the engine forget what it learned about finding that object's properties.
“dict.hasOwnProperty always works.”
Not on a null-prototype dictionary. It has no prototype, so it has no hasOwnProperty method. Use Object.hasOwn(dict, key) instead.
“__proto__ is always a normal property name.”
On a normal object, writing to __proto__ runs an old built-in setter that changes the prototype. On a null-prototype object or a Map, __proto__ is just a key.
Practice: create, inspect, and count safely
5 EXERCISESRead the code and predict the three lines it prints.
const base = { kind: "base" };
const child = Object.create(base);
console.log(Object.getPrototypeOf(child) === base);
console.log("kind" in child);
console.log(Object.hasOwn(child, "kind"));The lines are true, true, and false. The prototype of child is base. "kind" in child finds kind through the link. But child itself doesn't own kind.
The starter counts words such as apple correctly, but not constructor. Make the dictionary safe, then type the count it should print.
function countWords(words) {
const counts = {};
for (const word of words) {
counts[word] = (counts[word] || 0) + 1;
}
return counts;
}
console.log(countWords(["constructor", "constructor"]).constructor);function countWords(words) {
const counts = Object.create(null);
for (const word of words) {
counts[word] = (counts[word] || 0) + 1;
}
return counts;
}
console.log(countWords(["constructor", "constructor"]).constructor);Object.create(null) has no inherited names. So counts.constructor starts as undefined, not as a function from Object.prototype. The fixed counter prints 2. A Map with get and set also works.
Make an object linked to shared. Then answer: does the new object own the method?
const shared = { describe() { return "shared method"; } };
const item = Object.create(shared);
item.name = "item";
console.log(item.describe());
console.log(Object.hasOwn(item, "describe"));item.describe() works because JavaScript follows the link to shared, so it prints shared method. Object.hasOwn(item, "describe") is false: the method is inherited, not copied.
Finish the advice with one word: set prototypes at ___ time.
Set prototypes at creation time. setPrototypeOf exists for rare cases. Changing the link of an object that is already in use is slow, because the engine has to forget what it learned about that object.
Which function lists the key-value pairs of the dictionary, without calling a method on the dictionary?
const dict = Object.create(null);
dict.apple = 2;
dict.constructor = 1;
for (const [key, value] of Object.entries(dict)) {
console.log(key + ":" + value);
}const dict = Object.create(null);
dict.apple = 2;
dict.constructor = 1;
for (const [key, value] of Object.entries(dict)) {
console.log(key + ":" + value);
}Object.entries(dict) gives the own enumerable key-value pairs. It doesn't need dict to inherit anything. The loop prints apple:2 and constructor:1.
Quiz: check your understanding
7 QUESTIONSAnswer each question, then read why each choice is right or wrong.
Question 1 of 7What does
Object.create(proto)do?Choose an answer to see the explanation.
Question 2 of 7What does this inherited property check print?
Read the code, then predictconst base = { answer: 42 }; const child = Object.create(base); console.log(Object.hasOwn(child, "answer"));Choose an answer to see the explanation.
Question 3 of 7Why should code that runs often avoid
Object.setPrototypeOf(obj, proto)?Choose an answer to see the explanation.
Question 4 of 7What does this null-prototype dictionary print?
Read the code, then predictconst dict = Object.create(null); dict.constructor = 2; console.log(dict.constructor);Choose an answer to see the explanation.
Question 5 of 7Which check works safely for a null-prototype dictionary?
Choose an answer to see the explanation.
Question 6 of 7What happens on a plain object when you assign
obj["__proto__"] = { polluted: true }?Choose an answer to see the explanation.
Question 7 of 7What does this print?
Read the code, then predictconst a = {}; try { Object.setPrototypeOf(a, a); } catch (error) { console.log(error.name); }Choose an answer to see the explanation.
Key takeaways
Object.create(proto)makes a new object linked toproto. It doesn't copy anything.- Its second argument takes property descriptors. Any flag you leave out (
writable,enumerable,configurable) isfalse. Object.getPrototypeOfreads the link.Object.setPrototypeOfchanges it, but that is slow, so use it rarely.Object.create(null)makes a dictionary with no prototype: no inheritedtoString,constructor, orhasOwnProperty.- When keys come from data, use
Object.hasOwn,Object.entries, or aMap.
Remember the one-liner.Object.create picks an object's prototype when the object is made. Object.create(null) picks no prototype at all.
Up next: Delegation vs classical inheritance.