ESC

Type to search the knowledge base.

Object.create and the prototype chain

Build objects with Object.create, walk [[Prototype]], distinguish own vs inherited props, and know null-prototype maps.

intermediate3 min read
  • javascript
  • object-create

Every ordinary object has an internal [[Prototype]] link. Property lookup walks that chain until it finds a name or hits null. Object.create(proto) builds a new object whose prototype is exactly proto.

const animal = {
  eat() { return `${this.name} eats`; },
};

const dog = Object.create(animal);
dog.name = 'Rex';
dog.eat(); // 'Rex eats' — method found on prototype, this is dog

Own vs inherited

const base = { a: 1 };
const child = Object.create(base);
child.b = 2;

child.hasOwnProperty('a'); // false
child.hasOwnProperty('b'); // true
'a' in child;              // true (walks chain)
Object.keys(child);        // ['b'] — own enumerable only
Check Own only Chain
obj.prop / in ✓
Object.keys / hasOwn ✓
for...in enumerable on chain ✓

Prefer Object.hasOwn(obj, key) (or Object.prototype.hasOwnProperty.call) over assuming hasOwnProperty exists — objects can shadow it.

Object.create signature

Object.create(proto, propertiesObject?);
const point = Object.create(Object.prototype, {
  x: { value: 0, writable: true, enumerable: true, configurable: true },
  y: { value: 0, writable: true, enumerable: true, configurable: true },
});

Second arg is property descriptors — same shape as Object.defineProperties.

Null-prototype objects

const map = Object.create(null);
map.__proto__; // undefined — no Object.prototype
map.toString;  // undefined

// Safe dictionary: keys won’t clash with 'toString', 'constructor', etc.
map['toString'] = 'safe';

Useful for maps keyed by arbitrary strings (user input). Tradeoff: no hasOwnProperty method — use Object.hasOwn(map, k).

__proto__ vs Object.getPrototypeOf

Object.getPrototypeOf(dog) === animal; // true
Object.setPrototypeOf(dog, other);     // possible, usually slow — avoid hot paths

obj.__proto__ is legacy accessor on Object.prototype; don’t use it in new code. Setting prototypes after creation deopts engines.

vs class / constructor

function Dog(name) {
  this.name = name;
}
Dog.prototype.bark = function () { return 'woof'; };

const d = new Dog('Rex');
Object.getPrototypeOf(d) === Dog.prototype;

new sets [[Prototype]] to Constructor.prototype. Object.create(Dog.prototype) without new skips constructor body — pattern for subclassing in old code:

function Terrier(name) {
  Dog.call(this, name);
}
Terrier.prototype = Object.create(Dog.prototype);
Terrier.prototype.constructor = Terrier;

Today: class Terrier extends Dog.

Interview answer (out loud)

“Property lookup walks the prototype chain. Object.create(proto) makes an object with that prototype. Own properties sit on the object; inherited ones live up the chain. Object.create(null) makes a pure dictionary without Object.prototype. Prefer get/setPrototypeOf and hasOwn over proto hacks.”

Checking the chain

function getProtoChain(obj) {
  const chain = [];
  let cur = obj;
  while (cur) {
    chain.push(cur);
    cur = Object.getPrototypeOf(cur);
  }
  return chain; // ends before null
}

Object.getPrototypeOf(Object.prototype); // null

Property shadowing

const p = { x: 1 };
const c = Object.create(p);
c.x = 2; // own property shadows
delete c.x;
c.x; // 1 again from prototype

Assignment usually creates an own data property (unless an accessor on the chain traps it). Understanding shadowing explains unexpected delete behavior.

Further reading

Related guides