docs/concepts/object-creation-prototypes.mdx
How does a plain JavaScript object know about methods like .toString() or .hasOwnProperty() that you never defined? How does JavaScript let objects inherit from other objects without traditional classes?
// You create a simple object
const player = { name: "Alice", health: 100 }
// But it has methods you never defined!
console.log(player.toString()) // "[object Object]"
console.log(player.hasOwnProperty("name")) // true
// Where do these come from?
console.log(Object.getPrototypeOf(player)) // { constructor: Object, toString: f, ... }
The answer is the prototype chain. It's JavaScript's inheritance mechanism, defined in the ECMAScript specification as the [[Prototype]] internal slot. Every object has a hidden link to another object called its prototype. When you access a property, JavaScript looks for it on the object first, then follows this chain of prototypes until it finds the property or reaches the end (null).
The prototype chain is JavaScript's way of implementing inheritance. Every object has an internal link (called [[Prototype]]) to another object, its prototype. When you try to access a property on an object, JavaScript:
null (the end of the chain)// Create a simple object
const wizard = {
name: "Gandalf",
castSpell() {
return `${this.name} casts a spell!`
}
}
// Create another object that inherits from wizard
const apprentice = Object.create(wizard)
apprentice.name = "Harry"
// apprentice has its own 'name' property
console.log(apprentice.name) // "Harry"
// But castSpell comes from the prototype (wizard)
console.log(apprentice.castSpell()) // "Harry casts a spell!"
// The prototype chain:
// apprentice → wizard → Object.prototype → null
┌─────────────────────────────────────────────────────────────────────────┐
│ THE PROTOTYPE CHAIN │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ apprentice.castSpell() │
│ │ │
│ │ 1. Does apprentice have castSpell? NO │
│ ▼ │
│ ┌──────────────┐ │
│ │ apprentice │ │
│ │──────────────│ │
│ │ name: "Harry"│ │
│ │ [[Prototype]]│────┐ │
│ └──────────────┘ │ │
│ │ 2. Does wizard have castSpell? YES! Use it │
│ ▼ │
│ ┌──────────────────┐ │
│ │ wizard │ │
│ │──────────────────│ │
│ │ name: "Gandalf" │ │
│ │ castSpell: fn │ ◄── Found here! │
│ │ [[Prototype]] │────┐ │
│ └──────────────────┘ │ │
│ │ 3. If not found, keep going... │
│ ▼ │
│ ┌────────────────────┐ │
│ │ Object.prototype │ │
│ │────────────────────│ │
│ │ toString: fn │ │
│ │ hasOwnProperty: fn │ │
│ │ [[Prototype]] │────┐ │
│ └────────────────────┘ │ │
│ │ │
│ ▼ │
│ null │
│ (end of chain) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
[[Prototype]], __proto__, and .prototypeThese three terms confuse many developers. Let's clarify:
| Term | What It Is | How to Access |
|---|---|---|
[[Prototype]] | The internal prototype link every object has | Not directly accessible (it's internal) |
__proto__ | A getter/setter that exposes [[Prototype]] | obj.__proto__ (deprecated, avoid in production) |
.prototype | A property on functions used when creating instances with new | Function.prototype |
// Every object has [[Prototype]] — an internal link to its prototype
const player = { name: "Alice" }
// __proto__ exposes [[Prototype]] (deprecated but works)
console.log(player.__proto__ === Object.prototype) // true
// .prototype exists only on FUNCTIONS
function Player(name) {
this.name = name
}
// When you use 'new Player()', the new object's [[Prototype]]
// is set to Player.prototype
console.log(Player.prototype) // { constructor: Player }
const alice = new Player("Alice")
console.log(Object.getPrototypeOf(alice) === Player.prototype) // true
┌─────────────────────────────────────────────────────────────────────────┐
│ THE THREE PROTOTYPE TERMS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [[Prototype]] The hidden internal slot every object has │
│ ────────────── Points to the object's prototype │
│ You can't access it directly │
│ │
│ __proto__ A way to READ/WRITE [[Prototype]] │
│ ───────── obj.__proto__ = Object.getPrototypeOf(obj) │
│ DEPRECATED! Use Object.getPrototypeOf() instead │
│ │
│ .prototype A property that exists ONLY on functions │
│ ────────── Used as the [[Prototype]] for objects │
│ created with new │
│ │
│ ───────────────────────────────────────────────────────────────────── │
│ │
│ function Player(name) { this.name = name } │
│ │
│ Player.prototype ─────────────┐ │
│ │ │
│ const p = new Player("A") │ │
│ │ │ │
│ │ [[Prototype]] ════════╧═══▶ { constructor: Player } │
│ │ │ │
│ ▼ │ [[Prototype]] │
│ ┌───────────┐ ▼ │
│ │ p │ Object.prototype │
│ │───────────│ │ │
│ │name: "A" │ │ [[Prototype]] │
│ └───────────┘ ▼ │
│ null │
│ │
└─────────────────────────────────────────────────────────────────────────┘
When you access a property on an object, JavaScript performs a prototype chain lookup:
<Steps> <Step title="Check the object itself"> JavaScript first looks for the property directly on the object. </Step> <Step title="Check the prototype"> If not found, it looks at `Object.getPrototypeOf(obj)` (the object's prototype). </Step> <Step title="Continue up the chain"> If still not found, it checks the prototype's prototype, and so on. </Step> <Step title="Reach null or find the property"> The search stops when the property is found OR when `null` is reached (property is `undefined`). </Step> </Steps>const grandparent = {
familyName: "Smith",
sayHello() {
return `Hello from the ${this.familyName} family!`
}
}
const parent = Object.create(grandparent)
parent.job = "Engineer"
const child = Object.create(parent)
child.name = "Alice"
// Property lookup in action:
console.log(child.name) // "Alice" (found on child)
console.log(child.job) // "Engineer" (found on parent)
console.log(child.familyName) // "Smith" (found on grandparent)
console.log(child.sayHello()) // "Hello from the Smith family!"
console.log(child.age) // undefined (not found anywhere)
// Visualizing the chain
console.log(Object.getPrototypeOf(child) === parent) // true
console.log(Object.getPrototypeOf(parent) === grandparent) // true
console.log(Object.getPrototypeOf(grandparent) === Object.prototype) // true
console.log(Object.getPrototypeOf(Object.prototype)) // null
When you set a property on an object, it creates or updates the property on that object, even if a property with the same name exists on the prototype:
const prototype = {
greeting: "Hello",
count: 0
}
const obj = Object.create(prototype)
// Reading — uses prototype's value
console.log(obj.greeting) // "Hello" (from prototype)
console.log(obj.count) // 0 (from prototype)
// Writing — creates property on obj, "shadows" the prototype's
obj.greeting = "Hi"
obj.count = 5
console.log(obj.greeting) // "Hi" (own property)
console.log(prototype.greeting) // "Hello" (unchanged!)
console.log(obj.count) // 5 (own property)
console.log(prototype.count) // 0 (unchanged!)
// Check what's "own" vs inherited
console.log(obj.hasOwnProperty("greeting")) // true (it's on obj now)
console.log(obj.hasOwnProperty("count")) // true
┌─────────────────────────────────────────────────────────────────────────┐
│ PROPERTY SHADOWING │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ BEFORE obj.greeting = "Hi" AFTER obj.greeting = "Hi" │
│ ────────────────────────── ───────────────────────── │
│ │
│ obj obj │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ (empty) │ │ greeting: "Hi" │ ◄── shadows │
│ │ [[Proto]]───┼──┐ │ [[Proto]]────────┼──┐ │
│ └─────────────┘ │ └──────────────────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ prototype prototype │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ greeting: "Hello" │ │ greeting: "Hello" │ hidden │
│ │ count: 0 │ │ count: 0 │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
│ obj.greeting returns "Hello" obj.greeting returns "Hi" │
│ (found on prototype) (found on obj, stops looking) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
JavaScript gives you several ways to create objects, each with different use cases:
The simplest way. Great for one-off objects:
// Object literal — prototype is automatically Object.prototype
const player = {
name: "Alice",
health: 100,
attack() {
return `${this.name} attacks!`
}
}
console.log(Object.getPrototypeOf(player) === Object.prototype) // true
Object.create() creates a new object with a specified prototype:
// Create a prototype object
const animalProto = {
speak() {
return `${this.name} makes a sound.`
},
eat(food) {
return `${this.name} eats ${food}.`
}
}
// Create objects that inherit from animalProto
const dog = Object.create(animalProto)
dog.name = "Rex"
dog.breed = "German Shepherd"
const cat = Object.create(animalProto)
cat.name = "Whiskers"
cat.color = "orange"
console.log(dog.speak()) // "Rex makes a sound."
console.log(cat.eat("fish")) // "Whiskers eats fish."
// Both share the same prototype
console.log(Object.getPrototypeOf(dog) === animalProto) // true
console.log(Object.getPrototypeOf(cat) === animalProto) // true
Pass null to create an object with no prototype. This is useful for dictionaries:
// Regular object inherits from Object.prototype
const regular = {}
console.log(regular.toString) // [Function: toString]
console.log("toString" in regular) // true
// Object with null prototype — truly empty
const dict = Object.create(null)
console.log(dict.toString) // undefined
console.log("toString" in dict) // false
// Useful for safe dictionaries (no inherited properties to collide with)
dict["hasOwnProperty"] = "I can use any key!"
console.log(dict["hasOwnProperty"]) // "I can use any key!"
// With regular object, this would shadow the method:
const risky = {}
risky["hasOwnProperty"] = "oops"
// risky.hasOwnProperty("x") would now throw an error!
You can define properties with descriptors:
const person = Object.create(Object.prototype, {
name: {
value: "Alice",
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 30,
writable: false, // Can't change age
enumerable: true,
configurable: false
},
secret: {
value: "hidden",
enumerable: false // Won't show in for...in or Object.keys()
}
})
console.log(person.name) // "Alice"
console.log(person.age) // 30
person.age = 25 // Silently fails (or throws in strict mode)
console.log(person.age) // Still 30
console.log(Object.keys(person)) // ["name", "age"] (no "secret")
new Operator — Create from ConstructorThe new operator creates an object from a constructor function. When you call new Constructor(args), JavaScript performs 4 steps:
// A constructor function
function Player(name, health) {
// Step 3: 'this' is bound to the new object
this.name = name
this.health = health
}
// Methods go on the prototype (shared by all instances)
Player.prototype.attack = function() {
return `${this.name} attacks!`
}
// Create instance with 'new'
const alice = new Player("Alice", 100)
console.log(alice.name) // "Alice"
console.log(alice.attack()) // "Alice attacks!"
console.log(alice instanceof Player) // true
console.log(Object.getPrototypeOf(alice) === Player.prototype) // true
┌─────────────────────────────────────────────────────────────────────────┐
│ WHAT new Player("Alice", 100) DOES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: Create a new empty object │
│ const obj = {} │
│ │
│ Step 2: Link the object's prototype to Constructor.prototype │
│ Object.setPrototypeOf(obj, Player.prototype) │
│ │
│ Step 3: Run the constructor with 'this' bound to the new object │
│ Player.call(obj, "Alice", 100) │
│ // Now obj.name = "Alice", obj.health = 100 │
│ │
│ Step 4: Return the object (unless constructor returns an object) │
│ return obj │
│ │
│ ───────────────────────────────────────────────────────────────────── │
│ │
│ RESULT: │
│ │
│ Player.prototype │
│ ┌─────────────────────┐ │
│ │ attack: function() │◄───── Shared by all instances │
│ │ constructor: Player │ │
│ └─────────────────────┘ │
│ ▲ │
│ │ [[Prototype]] │
│ │ │
│ ┌────────┴────────┐ │
│ │ alice │ │
│ │─────────────────│ │
│ │ name: "Alice" │ │
│ │ health: 100 │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
newHere's a function that does what new does:
function myNew(Constructor, ...args) {
// Steps 1 & 2: Create object with correct prototype
const obj = Object.create(Constructor.prototype)
// Step 3: Run constructor with 'this' = obj
const result = Constructor.apply(obj, args)
// Step 4: Return result if it's a non-primitive, otherwise return obj
// Note: Functions are also objects, so constructors returning functions
// will override the default return as well
return (result !== null && typeof result === 'object') ? result : obj
}
// These do the same thing:
const player1 = new Player("Alice", 100)
const player2 = myNew(Player, "Bob", 100)
console.log(player1 instanceof Player) // true
console.log(player2 instanceof Player) // true
Object.assign() copies enumerable own properties from source objects to a target:
// Basic usage: copy properties to target
const target = { a: 1 }
const source = { b: 2, c: 3 }
const result = Object.assign(target, source)
console.log(result) // { a: 1, b: 2, c: 3 }
console.log(target) // { a: 1, b: 2, c: 3 } — target is modified!
console.log(result === target) // true — returns the target
const defaults = { theme: "light", fontSize: 14, showSidebar: true }
const userPrefs = { theme: "dark", fontSize: 16 }
const sessionOverrides = { fontSize: 18 }
// Later sources overwrite earlier ones
const settings = Object.assign({}, defaults, userPrefs, sessionOverrides)
console.log(settings)
// { theme: "dark", fontSize: 18, showSidebar: true }
// Original objects are unchanged (because we used {} as target)
console.log(defaults.fontSize) // 14
const original = { name: "Alice", scores: [90, 85, 92] }
// Shallow clone
const clone = Object.assign({}, original)
clone.name = "Bob"
console.log(original.name) // "Alice" — primitive copied by value
clone.scores.push(100)
console.log(original.scores) // [90, 85, 92, 100] — array shared!
// Deep clone with structuredClone (modern browsers)
const deepClone = structuredClone(original)
deepClone.scores.push(100)
console.log(original.scores) // [90, 85, 92] — unchanged!
const proto = { inherited: "from prototype" }
const source = Object.create(proto)
source.own = "my own property"
Object.defineProperty(source, "hidden", {
value: "non-enumerable",
enumerable: false
})
const target = {}
Object.assign(target, source)
console.log(target.own) // "my own property" — copied
console.log(target.inherited) // undefined — NOT copied (inherited)
console.log(target.hidden) // undefined — NOT copied (non-enumerable)
JavaScript provides methods to work with prototypes:
const player = { name: "Alice" }
// Get the prototype
const proto = Object.getPrototypeOf(player)
console.log(proto === Object.prototype) // true
// Works with any object
function Game() {}
const game = new Game()
console.log(Object.getPrototypeOf(game) === Game.prototype) // true
// End of the chain
console.log(Object.getPrototypeOf(Object.prototype)) // null
Object.setPrototypeOf() changes an object's prototype after creation:
const swimmer = {
swim() { return `${this.name} swims!` }
}
const flyer = {
fly() { return `${this.name} flies!` }
}
const duck = { name: "Donald" }
// Start as a swimmer
Object.setPrototypeOf(duck, swimmer)
console.log(duck.swim()) // "Donald swims!"
// Change to a flyer
Object.setPrototypeOf(duck, flyer)
console.log(duck.fly()) // "Donald flies!"
// console.log(duck.swim()) // TypeError: duck.swim is not a function
The instanceof operator checks if Constructor.prototype exists in the object's prototype chain:
function Animal(name) {
this.name = name
}
function Dog(name, breed) {
Animal.call(this, name)
this.breed = breed
}
// Set up inheritance
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog
const rex = new Dog("Rex", "German Shepherd")
console.log(rex instanceof Dog) // true
console.log(rex instanceof Animal) // true
console.log(rex instanceof Object) // true
console.log(rex instanceof Array) // false
const animal = { eats: true }
const dog = Object.create(animal)
dog.barks = true
console.log(animal.isPrototypeOf(dog)) // true
console.log(Object.prototype.isPrototypeOf(dog)) // true
console.log(Array.prototype.isPrototypeOf(dog)) // false
These methods help you work with object properties and prototypes:
const proto = { inherited: true }
const obj = Object.create(proto)
obj.own = true
// hasOwnProperty checks ONLY the object, not the chain
console.log(obj.hasOwnProperty("own")) // true
console.log(obj.hasOwnProperty("inherited")) // false
// 'in' operator checks the whole chain
console.log("own" in obj) // true
console.log("inherited" in obj) // true
Use Object.hasOwn() instead of hasOwnProperty(). It's safer because it works on objects with a null prototype and can't be shadowed:
// hasOwnProperty can be shadowed or unavailable
const nullProto = Object.create(null)
nullProto.key = "value"
// nullProto.hasOwnProperty("key") // TypeError: not a function
// Object.hasOwn always works
Object.hasOwn(nullProto, "key") // true
const proto = { inherited: "value" }
const obj = Object.create(proto)
obj.own1 = "a"
obj.own2 = "b"
// Object.keys() — only own enumerable properties
console.log(Object.keys(obj)) // ["own1", "own2"]
// for...in — own AND inherited enumerable properties
for (const key in obj) {
console.log(key) // "own1", "own2", "inherited"
}
// Filter for...in to only own properties
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key) // "own1", "own2"
}
}
const obj = { visible: true }
Object.defineProperty(obj, "hidden", {
value: "secret",
enumerable: false
})
// Object.keys() — only enumerable
console.log(Object.keys(obj)) // ["visible"]
// Object.getOwnPropertyNames() — all own properties
console.log(Object.getOwnPropertyNames(obj)) // ["visible", "hidden"]
| Method | Own? | Enumerable? | Inherited? |
|---|---|---|---|
obj.hasOwnProperty(key) | Yes | Both | No |
key in obj | Yes | Both | Yes |
Object.keys(obj) | Yes | Yes only | No |
Object.getOwnPropertyNames(obj) | Yes | Both | No |
for...in | Yes | Yes only | Yes |
// ❌ NEVER do this!
Object.prototype.greet = function() {
return "Hello!"
}
// Now EVERY object has greet()
const player = { name: "Alice" }
const numbers = [1, 2, 3]
const date = new Date()
console.log(player.greet()) // "Hello!"
console.log(numbers.greet()) // "Hello!"
console.log(date.greet()) // "Hello!"
// This can break for...in loops
for (const key in player) {
console.log(key) // "name", "greet" — greet shows up!
}
// And cause conflicts with libraries
.prototype with [[Prototype]]function Player(name) {
this.name = name
}
const alice = new Player("Alice")
// ❌ WRONG — instances don't have .prototype
console.log(alice.prototype) // undefined
// ✓ CORRECT — use Object.getPrototypeOf()
console.log(Object.getPrototypeOf(alice) === Player.prototype) // true
// .prototype is ONLY on functions
console.log(Player.prototype) // { constructor: Player }
Prototype pollution occurs when attackers can modify Object.prototype, affecting all objects. This is a real security vulnerability:
// ❌ DANGEROUS - merging untrusted data can pollute prototypes
const maliciousPayload = JSON.parse('{"__proto__": {"isAdmin": true}}')
const user = {}
Object.assign(user, maliciousPayload) // Pollution via Object.assign!
// Now ALL objects have isAdmin!
const anotherUser = {}
console.log(anotherUser.isAdmin) // true - polluted!
// ✓ SAFER - use null prototype objects for dictionaries
const safeDict = Object.create(null)
safeDict["__proto__"] = "safe" // Just a regular property, no pollution
// ✓ SAFEST - use Map for key-value storage with untrusted keys
const map = new Map()
map.set("__proto__", "value") // Completely safe
// ❌ WRONG — array on prototype is shared by all instances
function Player(name) {
this.name = name
}
Player.prototype.inventory = [] // Shared by ALL players!
const alice = new Player("Alice")
const bob = new Player("Bob")
alice.inventory.push("sword")
console.log(bob.inventory) // ["sword"] — Bob has Alice's sword!
// ✓ CORRECT — initialize arrays in constructor
function Player(name) {
this.name = name
this.inventory = [] // Each player gets their own array
}
Every object has a prototype — a hidden link ([[Prototype]]) to another object, forming a chain that ends at null
Property lookup walks the chain — JavaScript searches the object first, then its prototype, then the prototype's prototype, and so on
[[Prototype]] vs .prototype — [[Prototype]] is the internal link every object has; .prototype is a property on functions used with new
Use Object.getPrototypeOf() — not __proto__, which is deprecated
Object.create(proto) — creates an object with a specific prototype; pass null for no prototype
The new operator does 4 things — creates object, links prototype, runs constructor with this, returns the object
Object.assign() is shallow — nested objects are copied by reference, not cloned
hasOwnProperty() vs in — hasOwnProperty checks only the object; in checks the whole prototype chain
Never modify Object.prototype — it affects all objects and can break code
Put methods on the prototype — for memory efficiency, don't define methods in the constructor
</Info>The prototype chain is JavaScript's inheritance mechanism. Every object has a `[[Prototype]]` link to another object (its prototype).
When you access a property:
1. JavaScript looks for it on the object itself
2. If not found, looks on the object's prototype
3. Continues up the chain until found or `null` is reached
```javascript
const parent = { greet: "Hello" }
const child = Object.create(parent)
console.log(child.greet) // "Hello" — found on prototype
console.log(child.missing) // undefined — not found anywhere
```
- **`[[Prototype]]`**: The internal slot every object has, pointing to its prototype. Not directly accessible.
- **`__proto__`**: A deprecated getter/setter that exposes `[[Prototype]]`. Use `Object.getPrototypeOf()` instead.
- **`.prototype`**: A property that exists **only on functions**. When you use `new`, the created object's `[[Prototype]]` is set to this value.
```javascript
function Foo() {}
const f = new Foo()
// f's [[Prototype]] is Foo.prototype
Object.getPrototypeOf(f) === Foo.prototype // true
// Foo is a function, so it has .prototype
Foo.prototype // { constructor: Foo }
// f is NOT a function, so it has no .prototype
f.prototype // undefined
```
When you call `new Constructor(args)`:
1. **Create** a new empty object `{}`
2. **Link** the object's `[[Prototype]]` to `Constructor.prototype`
3. **Execute** the constructor with `this` bound to the new object
4. **Return** the object (unless the constructor returns a different object)
```javascript
function myNew(Constructor, ...args) {
const obj = Object.create(Constructor.prototype) // Steps 1-2
const result = Constructor.apply(obj, args) // Step 3
return (typeof result === 'object' && result !== null) ? result : obj // Step 4
}
```
- **`Object.create(proto)`** creates an object with the specified object as its prototype. It doesn't call any constructor.
- **`new Constructor()`** creates an object with `Constructor.prototype` as its prototype AND runs the constructor function.
```javascript
const proto = { greet() { return "Hi!" } }
// Object.create — just links the prototype
const obj1 = Object.create(proto)
// new — links prototype AND runs constructor
function MyClass() {
this.initialized = true
}
MyClass.prototype = proto
const obj2 = new MyClass()
console.log(obj2.initialized) // true (constructor ran)
console.log(obj1.initialized) // undefined (no constructor)
```
Modifying `Object.prototype` affects **every object** in your application because all objects inherit from it. This can:
1. Break `for...in` loops (new properties show up)
2. Conflict with third-party libraries
3. Cause unexpected behavior throughout your codebase
```javascript
// ❌ BAD
Object.prototype.bad = "affects everything"
const obj = {}
for (const key in obj) {
console.log(key) // "bad" — unexpected!
}
```
Instead, create your own constructors/classes or use composition.
**Shallow copy**: Copies the top-level properties. Nested objects/arrays are copied by reference (they point to the same data).
**Deep copy**: Recursively copies all levels. Nested objects/arrays are fully cloned.
```javascript
const original = {
name: "Alice",
scores: [90, 85] // nested array
}
// Shallow copy with Object.assign
const shallow = Object.assign({}, original)
shallow.scores.push(100)
console.log(original.scores) // [90, 85, 100] — modified!
// Deep copy with structuredClone
const deep = structuredClone(original)
deep.scores.push(100)
console.log(original.scores) // [90, 85, 100] — still modified from before
// But if we had deep copied first, original would be unchanged
```