One function builds an object with the chain you choose, and one operator answers whether a given prototype appears anywhere along it.
Complete lesson & earn 250 PX
EXERCISE
1Rather than making an object and then rewiring it, this builds one with the link already in place.
EXERCISE
2You can get every benefit of a shared prototype without writing a constructor function or using new at all.
EXERCISE
3The operator does not check a type tag; it walks the prototype chain looking for one specific object.
EXERCISE
4Separating an object's own properties from the ones it inherits is a distinction the language gives you several tools for.
Lesson 1 used Object.setPrototypeOf to wire two objects together, with a warning attached. Here is the warning made concrete, and then the tool you should actually reach for.
When a engine sees you creating many objects of the same shape, it builds an internal optimisation for that shape so property access is fast. Changing an object's prototype after the fact invalidates that work. The engine has to discard what it learned and fall back to a slower path, and it does not just affect the one object. This is why MDN carries an explicit performance warning on setPrototypeOf and why you will almost never see it in shipped code.
Object.create avoids the problem entirely by establishing the link at the moment of creation. Nothing is rewired because nothing was ever wired differently.
The basic form:
const animal = {
breathe() {
return `${this.name} breathes`;
},
};
const dog = Object.create(animal);
dog.name = "Rex";
console.log(dog.breathe()); // Rex breathes
console.log(Object.getPrototypeOf(dog) === animal); // true
console.log(dog.hasOwnProperty("breathe")); // false
One argument: the object you want as the prototype. You get back a brand new empty object already linked to it.
Which puts the three ways of making an object on one footing:
const a = {};
console.log(Object.getPrototypeOf(a) === Object.prototype); // true
const b = Object.create(Object.prototype);
console.log(Object.getPrototypeOf(b) === Object.prototype); // true
const c = Object.create(null);
console.log(Object.getPrototypeOf(c)); // null
The first two lines produce the same thing. A literal {} is shorthand for Object.create(Object.prototype), which reframes the object literal you have written thousands of times as a special case of a more general operation.
A literal {} is shorthand for Object.create(Object.prototype). That reframes the object literal you have written thousands of times as a special case of one general operation, and it is why Object.create(null) is not an exotic trick but the same call with a different argument.
The pattern below has a name, the factory function, and it is a perfectly respectable way to build objects. Some codebases prefer it precisely because there is no new to forget and no constructor rules to remember.
And it lets you get the benefits of prototypes without a constructor at all:
const personMethods = {
greet() {
return `Hi, ${this.name}`;
},
isAdult() {
return this.age >= 18;
},
};
function createPerson(name, age) {
const person = Object.create(personMethods);
person.name = name;
person.age = age;
return person;
}
const ada = createPerson("Ada", 36);
console.log(ada.greet()); // Hi, Ada
console.log(ada.isAdult()); // true
console.log(Object.getPrototypeOf(ada) === personMethods); // true
No new, no .prototype, no constructor function. Every person shares one copy of greet and isAdult and carries their own name and age. This pattern has a name, the factory function, and it is a perfectly respectable way to build objects. Some codebases prefer it precisely because there is no new to forget and no this binding to lose.
There is a second argument most people never discover:
const config = Object.create(null, {
apiUrl: { value: "https://api.example.com", enumerable: true },
retries: { value: 3, enumerable: true, writable: false },
});
console.log(config.apiUrl); // https://api.example.com
config.retries = 99; // silently ignored: not writable
console.log(config.retries); // 3
It takes property descriptors, the same shape Object.defineProperty uses, letting you control whether each property can be written, enumerated or reconfigured. It is verbose enough that you will rarely want it, but knowing it exists explains why Object.create has a second parameter in the documentation.
Pro Tip:
Object.createis the honest version of whatnewdoes, with the object-building step left visible instead of hidden behind a keyword. Once you have written that factory by hand, lesson 4's four-step description ofnewreads as a summary of work you have already done rather than a rule handed to you.
instanceof looks like a type check and is not one. It is a question about ancestry, and once you know what it actually does, several results that otherwise look like bugs become obvious.
Start with the result that surprises people:
function Person(name) {
this.name = name;
}
const ada = new Person("Ada");
console.log(ada instanceof Person); // true
console.log(ada instanceof Object); // true
Both true. If instanceof were checking "what type is this", only one could be. What it actually asks is: is this constructor's .prototype object anywhere in the chain above me? Person.prototype is one step up. Object.prototype is two steps up. Both are in the chain, so both answer true.
Written out, the operator is a short loop you could have written yourself:
function myInstanceOf(obj, Constructor) {
let current = Object.getPrototypeOf(obj);
while (current !== null) {
if (current === Constructor.prototype) return true;
current = Object.getPrototypeOf(current);
}
return false;
}
console.log(myInstanceOf(ada, Person)); // true
console.log(myInstanceOf(ada, Object)); // true
console.log(myInstanceOf(ada, Array)); // false
That is very close to the real implementation. Walk up from the object, compare each link against one specific target, stop at null.
Which explains two behaviours that look like bugs and are not:
// 1. Nothing checks HOW the object was made. Only where it points.
const dog = {};
Object.setPrototypeOf(dog, Person.prototype);
console.log(dog instanceof Person); // true, though `new Person` never ran
// 2. Replace .prototype and existing instances stop matching.
const before = new Person("Ada");
Person.prototype = {};
console.log(before instanceof Person); // false
In the second case before is unchanged; it still links to the object Person.prototype used to hold. What moved was Person.prototype itself, so the comparison now looks for a different object and never finds it. Neither result is a flaw. Both follow directly from the loop above.
And two places where it is the wrong tool:
console.log("abc" instanceof String); // false
console.log(new String("abc") instanceof String); // true
console.log([] instanceof Array); // true
console.log(Array.isArray([])); // true
A primitive string has no prototype chain of its own, so the check fails even though the value clearly is a string. And while [] instanceof Array works in one document, it returns false for an array created inside an iframe or a worker, because that context has its own separate Array with its own separate .prototype. Array.isArray was added precisely to answer the question reliably across those boundaries.
So the practical guidance:
// "Is this an array?" -> Array.isArray(value)
// "Is this a string/number?" -> typeof value
// "Is this null?" -> value === null
// "Is this one of MY types?" -> instanceof
Pro Tip:
instanceofis about membership of a hierarchy you built, not about what kind of value something is. That is a narrow job and it does it well: Unit 9 uses it to tell your own error classes apart in acatchblock, which is exactly the case it was designed for. For anything built into the language, reach for the purpose-built check instead.
Now that objects can inherit, "does this object have property X" splits into two different questions. Does it have one of its own, or can it merely reach one? Different tools answer differently, and choosing the wrong one produces bugs that are hard to see.
in searches the whole chain. hasOwnProperty does not:
const animal = { legs: 4 };
const dog = Object.create(animal);
dog.name = "Rex";
console.log("name" in dog); // true
console.log("legs" in dog); // true <- inherited
console.log("toString" in dog); // true <- from Object.prototype
console.log(dog.hasOwnProperty("name")); // true
console.log(dog.hasOwnProperty("legs")); // false
Neither is more correct. They answer different questions. in means "can I reach this name from here", hasOwnProperty means "is this name stored on this object".
Object.hasOwn is the modern replacement, and the reason it exists is worth knowing:
console.log(Object.hasOwn(dog, "name")); // true
console.log(Object.hasOwn(dog, "legs")); // false
// hasOwnProperty is itself inherited from Object.prototype,
// so it is missing exactly where you most need it:
const bare = Object.create(null);
bare.name = "Ada";
// bare.hasOwnProperty("name"); TypeError: not a function
console.log(Object.hasOwn(bare, "name")); // true
There is a second failure too. An object can have its own property literally called hasOwnProperty, for instance if its keys came from user input or a payload, and then calling it does something entirely unintended. Object.hasOwn sidesteps both problems by being a function you call on the object rather than a method you call through it.
Iteration splits along the same line:
const animal = { legs: 4 };
const dog = Object.create(animal);
dog.name = "Rex";
for (const key in dog) console.log(key);
// name
// legs <- for-in climbs the chain
console.log(Object.keys(dog)); // [ 'name' ]
console.log(Object.entries(dog)); // [ [ 'name', 'Rex' ] ]
console.log(JSON.stringify(dog)); // {"name":"Rex"}
for...in is the only common iteration that climbs. Object.keys, Object.entries, Object.values and JSON.stringify all stop at own properties. That last line is worth noticing: an inherited value silently disappears when you serialise, which is a genuinely confusing bug when an object logs correctly in the console and then arrives at your missing fields.
Which is why older code is full of this guard:
for (const key in dog) {
if (!Object.hasOwn(dog, key)) continue;
console.log(key); // name
}
It was written before Object.keys existed, when for...in was the only way to walk an object and every loop needed protecting from inherited names. Today the guard is unnecessary because the tool that does not climb is right there.
Pro Tip:
for...inclimbing the chain is why it is the wrong loop for an array and the wrong loop for any object you plan to serialise. Reach forObject.keysorObject.entriesby default and you get own properties only, which is what you meant in almost every case. Savefor...infor the rare moment you genuinely want inherited keys as well.
Object.create(proto) makes a new object linked to proto, including null for no chain at all. instanceof walks the chain looking for a constructor's .prototype, so it answers about ancestry rather than about type.