Prototypes and Inheritance Β· Astro Tech Blog

Prototypes, Inheritance

JavaScript uses prototypal inheritance β€” objects can inherit properties from other objects.

Prototypal Inheritance

Every object has a hidden [[Prototype]] property that references another object (or null). When you access a property that doesn’t exist on the object, JavaScript looks it up on the prototype chain.

let animal = {
  eats: true,
  walk() {
    console.log("Animal walks");
  }
};

let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // Set prototype (old way)

console.log(rabbit.eats); // β†’ true (inherited from animal)
console.log(rabbit.jumps); // β†’ true (own property)

rabbit.walk(); // β†’ "Animal walks" (inherited)

The Prototype Chain

rabbit β†’ animal β†’ Object.prototype β†’ null
let animal = { eats: true };
let rabbit = { jumps: true };

rabbit.__proto__ = animal;

// rabbit β†’ animal β†’ Object.prototype β†’ null

console.log(rabbit.toString); // From Object.prototype

Writing to Prototype

Writing to a property only affects the object itself, not the prototype:

let animal = {
  eats: true,
  walk() {
    console.log("Animal walks");
  }
};

let rabbit = { __proto__: animal };

rabbit.walk = function() {
  console.log("Rabbit hops!");
};

rabbit.walk(); // β†’ "Rabbit hops!" (own method)
animal.walk(); // β†’ "Animal walks" (unchanged)

β€œthis” in Prototypes

The value of this is always the object before the dot, even if the method is on the prototype:

let animal = {
  name: "Animal",
  walk() {
    console.log(this.name + " walks");
  }
};

let rabbit = {
  __proto__: animal,
  name: "Rabbit"
};

rabbit.walk(); // β†’ "Rabbit walks" (this = rabbit)

for…in and Prototypes

for...in iterates over own + inherited enumerable properties:

let animal = { eats: true };
let rabbit = { jumps: true, __proto__: animal };

for (let key in rabbit) {
  let isOwn = rabbit.hasOwnProperty(key);
  console.log(`${key}: ${isOwn ? "own" : "inherited"}`);
}
// β†’ "jumps: own"
// β†’ "eats: inherited"

F.prototype

The F.prototype property (note: not [[Prototype]]) is used with the new operator.

function Animal(name) {
  this.name = name;
}

// Animal.prototype is the object that becomes [[Prototype]] of new instances
Animal.prototype.walk = function() {
  console.log(this.name + " walks");
};

let rabbit = new Animal("Rabbit");
let cat = new Animal("Cat");

rabbit.walk(); // β†’ "Rabbit walks"
cat.walk();    // β†’ "Cat walks"

// Both share the same walk method via prototype
console.log(rabbit.walk === cat.walk); // β†’ true

How it works

When new Animal() is called:

  1. A new object {} is created
  2. Its [[Prototype]] is set to Animal.prototype
  3. The constructor runs with this set to the new object
// Animal.prototype is just a regular object:
console.log(Animal.prototype); // β†’ { walk: [Function], constructor: Animal }
console.log(Animal.prototype.constructor === Animal); // β†’ true

Default F.prototype

Every function automatically has a prototype property pointing to an object with a single property constructor:

function Foo() {}

console.log(Foo.prototype.constructor === Foo); // β†’ true

let obj = new Foo();
console.log(obj.constructor === Foo); // β†’ true (inherited)

Change F.prototype

You can replace the prototype to provide a different set of methods:

function Rabbit() {}

// Replace the prototype entirely
Rabbit.prototype = {
  jumps: true
};

// But now constructor is gone!
let r = new Rabbit();
console.log(r.constructor === Rabbit); // β†’ false

// Fix it:
Rabbit.prototype = {
  jumps: true,
  constructor: Rabbit // Explicitly set constructor
};

Native Prototypes

Built-in objects like Array, String, Number all use prototypes.

// Array methods are on Array.prototype
let arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype); // β†’ true

// String methods are on String.prototype
let str = "hello";
console.log(str.__proto__ === String.prototype); // β†’ true

The Complete Chain

let arr = [1, 2, 3];

// arr β†’ Array.prototype β†’ Object.prototype β†’ null

console.log(arr.__proto__ === Array.prototype);        // β†’ true
console.log(arr.__proto__.__proto__ === Object.prototype); // β†’ true
console.log(arr.__proto__.__proto__.__proto__);            // β†’ null
// ❌ This works but DON'T do it in production
String.prototype.show = function() {
  console.log(this);
};

"hello".show(); // β†’ "hello" (but could conflict with future JS versions)

Polyfills (this is OK)

Adding methods to native prototypes is acceptable for polyfills:

if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(str) {
    return this.indexOf(str) === 0;
  };
}

Prototype Methods, Objects without proto

Modern ways to work with prototypes:

Modern Methods

let animal = { eats: true };
let rabbit = { jumps: true };

// Get/set prototype
console.log(Object.getPrototypeOf(rabbit)); // β†’ {} (default)

Object.setPrototypeOf(rabbit, animal);
console.log(Object.getPrototypeOf(rabbit)); // β†’ animal

// Create object with given prototype
let rabbit2 = Object.create(animal);
rabbit2.jumps = true;

Object.create

Create objects with a specific prototype and property descriptors:

let animal = { eats: true };

// Simple
let rabbit = Object.create(animal);
rabbit.jumps = true;

// With property descriptors
let rabbit2 = Object.create(animal, {
  jumps: {
    value: true,
    writable: true,
    enumerable: true,
    configurable: true
  }
});

Object.create for Object Copying

let clone = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);

Why avoid proto?

  • It’s a getter/setter on Object.prototype, which can cause performance issues
  • Not supported in all environments (though modern ones do)
  • The methods getPrototypeOf/setPrototypeOf are the proper way

Objects without Prototype

Create a truly plain object (no prototype chain):

let obj = Object.create(null);
// obj has NO prototype β€” no toString, no hasOwnProperty, etc.

obj.toString; // β†’ undefined (no Object.prototype)

console.log(Object.getPrototypeOf(obj)); // β†’ null

// Useful for pure dictionaries (no key collisions)
let dict = Object.create(null);
dict["toString"] = "value"; // No collision with Object.prototype.toString
Demo: Prototypes Demo
JavaScript
// Inheritance chain
let animal = {
eats: true,
sleep() {
return this.name + ' sleeps';
}
};

let rabbit = Object.create(animal);
rabbit.jumps = true;
rabbit.name = 'Buggs';

document.write('Eats: ' + rabbit.eats + '<br>');
document.write('Jumps: ' + rabbit.jumps + '<br>');
document.write(rabbit.sleep() + '<br>');

// Prototype chain
document.write('<br>Prototype chain:<br>');
document.write('rabbit β†’ animal: ' + (Object.getPrototypeOf(rabbit) === animal) + '<br>');
document.write('animal β†’ Object.prototype: ' + (Object.getPrototypeOf(animal) === Object.prototype) + '<br>');
document.write('Object.prototype β†’ null: ' + (Object.getPrototypeOf(Object.prototype) === null) + '<br>');

// hasOwnProperty
document.write('<br>Own vs inherited:<br>');
for (let key in rabbit) {
if (Object.hasOwn(rabbit, key)) {
document.write(key + ': own<br>');
} else {
document.write(key + ': inherited<br>');
}
}
Live Output Window