What Is [[Prototype]]?
Every object in JavaScript has an internal property called [[Prototype]] (double brackets indicate it's an engine-level slot, not a regular property). For regular objects created with {}, this slot points to Object.prototype. For instances created with new SomeClass(), it points to SomeClass.prototype.
const obj = { name: 'Alice' };
// Standard way to read [[Prototype]]
const proto = Object.getPrototypeOf(obj);
console.log(proto === Object.prototype); // true
// __proto__ is the old, non-standard accessor (still works but deprecated)
console.log(obj.__proto__ === Object.prototype); // true
// Check if a property is own (on the object itself) vs inherited
console.log(obj.hasOwnProperty('name')); // true β own property
console.log(obj.hasOwnProperty('toString')); // false β inherited from Object.prototype
// Verify toString lives on Object.prototype
console.log(Object.prototype.hasOwnProperty('toString')); // true
true true true false true
The Prototype Chain Lookup Algorithm
When you access obj.someProperty, JavaScript follows these steps:
- Check if
objhas an own property namedsomePropertyβ if yes, return it. - Follow
[[Prototype]]to the next object in the chain. - Repeat step 1β2 until the property is found or
[[Prototype]]isnull. - If
nullis reached without finding the property, returnundefined.
/*
Chain visualisation (ASCII):
rex (Dog instance)
ββ name: 'Rex'
ββ breed: 'Labrador'
ββ [[Prototype]] β Dog.prototype
ββ bark()
ββ [[Prototype]] β Animal.prototype
ββ eat()
ββ sleep()
ββ [[Prototype]] β Object.prototype
ββ toString()
ββ hasOwnProperty()
ββ [[Prototype]] β null
*/
class Animal {
eat() { return `${this.name} is eating`; }
sleep() { return `${this.name} is sleeping`; }
}
class Dog extends Animal {
constructor(name, breed) {
super();
this.name = name;
this.breed = breed;
}
bark() { return `${this.name} says: Woof!`; }
}
const rex = new Dog('Rex', 'Labrador');
// Own property lookup
console.log(rex.hasOwnProperty('name')); // true β on rex itself
// One level up β Dog.prototype
console.log(rex.bark()); // found on Dog.prototype
// Two levels up β Animal.prototype
console.log(rex.eat()); // found on Animal.prototype
// Three levels up β Object.prototype
console.log(rex.toString()); // found on Object.prototype
// Not found anywhere β undefined (not an error)
console.log(rex.fly); // undefined
true Rex says: Woof! Rex is eating [object Object] undefined
Function.prototype and Constructor.prototype
Every function object has a prototype property (not to be confused with [[Prototype]]). When you call a function with new, the resulting instance's [[Prototype]] is set to that function's prototype property.
function Greeter(name) {
this.name = name;
}
// Methods added to .prototype are shared by all instances
Greeter.prototype.hello = function () {
return `Hello, I'm ${this.name}`;
};
const g1 = new Greeter('Alice');
const g2 = new Greeter('Bob');
// Both instances share the SAME hello function (not copied)
console.log(g1.hello === g2.hello); // true β shared reference!
console.log(g1.hello());
console.log(g2.hello());
// Instance's [[Prototype]] equals Greeter.prototype
console.log(Object.getPrototypeOf(g1) === Greeter.prototype); // true
// Greeter.prototype's [[Prototype]] is Object.prototype
console.log(Object.getPrototypeOf(Greeter.prototype) === Object.prototype); // true
true Hello, I'm Alice Hello, I'm Bob true true
Object.create() for Prototypal Inheritance
Object.create(proto) creates a new object whose [[Prototype]] is set to proto. This is the most direct way to set up prototypal inheritance without classes.
const vehicleProto = {
start() { return `${this.make} ${this.model} engine started`; },
stop() { return `${this.make} ${this.model} engine stopped`; },
describe() { return `${this.year} ${this.make} ${this.model}`; }
};
function createVehicle(make, model, year) {
const v = Object.create(vehicleProto);
v.make = make;
v.model = model;
v.year = year;
return v;
}
const car = createVehicle('Toyota', 'Camry', 2023);
console.log(car.describe());
console.log(car.start());
console.log(Object.getPrototypeOf(car) === vehicleProto); // true
// Null prototype β object with NO inherited properties
const pure = Object.create(null);
pure.key = 'value';
console.log(pure.key); // value
console.log(pure.toString); // undefined β no Object.prototype!
2023 Toyota Camry Toyota Camry engine started true value undefined
Own vs Inherited Properties
| Method / Operator | Sees own props | Sees inherited props | Notes |
|---|---|---|---|
hasOwnProperty(key) | Yes | No | Best way to check own |
Object.keys(obj) | Yes (enumerable) | No | Most common iteration |
Object.getOwnPropertyNames(obj) | Yes (all) | No | Includes non-enumerable |
for...in | Yes | Yes | Iterates full chain |
in operator | Yes | Yes | 'toString' in obj is true |
JSON.stringify | Yes (enumerable) | No | Safe for serialisation |
Classes Are Syntactic Sugar Over Prototypes
ES6 classes don't introduce a new inheritance model. They are convenient syntax for setting up function constructors and linking .prototype chains. You can verify this:
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a noise.`; }
}
class Dog extends Animal {
speak() { return `${this.name} barks.`; }
}
const d = new Dog('Rex');
// Under the hood β prototype chain
console.log(typeof Dog); // 'function' β class IS a function
console.log(Dog.prototype.constructor === Dog); // true
console.log(Object.getPrototypeOf(Dog) === Animal); // true β static chain
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true β instance chain
console.log(d.speak());
function true true true Rex barks.
Never allow user-controlled data to set properties via key paths like obj['__proto__']['admin'] = true. This is the prototype pollution attack: it mutates Object.prototype and affects every plain object in the application. Always sanitise keys and use Object.create(null) for lookup maps in security-sensitive code.
The newer Object.hasOwn(obj, key) is safer than obj.hasOwnProperty(key) because it works even on objects created with Object.create(null) (which have no inherited hasOwnProperty method). Available in Node 16+ and all modern browsers.
ποΈ Practical Exercise
Without using the class keyword, create a prototype-based inheritance chain for Person β Employee β Manager:
- Use function constructors and
.prototypeassignment for methods. - Set up the chain with
Object.create(Person.prototype). - Add a method to each level and verify the full chain with
Object.getPrototypeOf(). - Then recreate the same hierarchy using ES6
classand confirm the runtime behaviour is identical.
π₯ Challenge Exercise
Write a function getFullChain(obj) that returns an array of all objects in the prototype chain of obj, from the object itself up to (but not including) null. Each entry should include the constructor name and a list of own method names. Test it on a three-level class hierarchy.
Interview Questions
- What is the difference between
__proto__andObject.getPrototypeOf()? - What is at the top of every prototype chain in JavaScript?
- What does
Object.create(null)produce and when is it useful? - How do
hasOwnPropertyand theinoperator differ? - Are ES6 classes a different inheritance system from prototypes?
- What is prototype pollution and how can you prevent it?
π Summary
- Every object has a
[[Prototype]]link; property lookups walk this chain untilnull. - Use
Object.getPrototypeOf(obj)β notobj.__proto__β to inspect the chain. Object.prototypeis at the top; it inherits fromnull.hasOwnProperty/Object.hasOwncheck only the object itself, not the chain.Object.create(proto)creates objects with an explicit prototype β pure prototypal inheritance without classes.- ES6 classes are syntactic sugar; the runtime still uses the same prototype mechanism.
- Prototype pollution is a security vulnerability β never allow arbitrary key writes to prototypes.
Frequently Asked Questions
Function.prototype is a regular property on function objects that becomes the [[Prototype]] of instances created with new. [[Prototype]] is the internal slot on every object that forms the inheritance chain. They are related but distinct: Function.prototype is the source; [[Prototype]] is the link.
Yes, with Object.setPrototypeOf(obj, newProto). However, this is a very slow operation because the engine must invalidate its internal optimisations. Prefer setting the prototype at creation time via Object.create() or the class syntax.
for...in iterates all enumerable properties including inherited ones. If someone has polluted a prototype (e.g., added a custom property to Object.prototype), it will appear in every for...in loop. Always guard with Object.hasOwn(obj, key) or use Object.keys() instead.