Prototypes & Inheritance

How does every array have a map method, when you never wrote one? Through prototypes. Every object has a hidden link to another object, its prototype, and when a property isn't found on the object itself, JavaScript looks it up there. Classes are built on top of this mechanism.

The prototype chain

JS chain.js
const animal = {
  eats: true,
  describe() {
    return `${this.name} eats: ${this.eats}`;
  },
};

const rabbit = Object.create(animal);
rabbit.name = "Bun";

console.log(rabbit.name);
console.log(rabbit.eats);
console.log(rabbit.describe());
console.log(Object.getPrototypeOf(rabbit) === animal);
Output
Bun
true
Bun eats: true
true

Object.create(animal) makes a new, empty object whose prototype is animal. rabbit has only a name of its own; eats and describe are found by following the link to animal. Inside describe, this is still rabbit, because that's the object the method was called on.

Own vs inherited properties

JS own.js
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;

console.log(Object.keys(rabbit));
console.log(Object.hasOwn(rabbit, "eats"), "eats" in rabbit);

rabbit.eats = false;
console.log(rabbit.eats, animal.eats);
Output
[ 'jumps' ]
false true
false true

Reading looks up the chain, but writing always creates or changes a property on the object itself. Setting rabbit.eats "shadows" the inherited value without affecting animal.

Built-in prototypes

Arrays inherit from Array.prototype, which inherits from Object.prototype, whose prototype is null — the end of the chain:

JS builtins.js
const list = [1, 2, 3];

console.log(Object.getPrototypeOf(list) === Array.prototype);
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));
console.log(Object.hasOwn(list, "map"), typeof list.map);
Output
true
true
null
false function

All arrays share one copy of map, filter and friends on Array.prototype, instead of each array carrying its own. That's the efficiency prototypes buy you.

Don't modify built-in prototypes. Adding methods to Array.prototype or Object.prototype affects every array or object in the page — including inside libraries — and can clash with future versions of JavaScript.

Constructor functions

Before classes existed, objects were created with constructor functions and new. Shared methods were put on the function's prototype property:

JS constructor.js
function Person(name) {
  this.name = name;
}

Person.prototype.greet = function () {
  return `Hi, I'm ${this.name}`;
};

const ana = new Person("Ana");
const ben = new Person("Ben");

console.log(ana.greet(), "|", ben.greet());
console.log(ana.greet === ben.greet);
console.log(Object.getPrototypeOf(ana) === Person.prototype);
Output
Hi, I'm Ana | Hi, I'm Ben
true
true

Calling a function with new does four things: creates an empty object, sets its prototype to Person.prototype, runs the function with this as the new object, and returns it. You'll see this pattern in older code and libraries.

Classes are the same thing, nicer to write

JS class.js
class Person {
  constructor(name) {
    this.name = name;
  }
  greet() {
    return `Hi, I'm ${this.name}`;
  }
}

const ana = new Person("Ana");
console.log(typeof Person);
console.log(Object.hasOwn(Person.prototype, "greet"));
console.log(ana.greet());
Output
function
true
Hi, I'm Ana

A class is a function, and its methods live on Person.prototype exactly as before. Classes add clearer syntax and a few safety features (they must be called with new, and their bodies run in strict mode). The next two lessons cover them properly.

instanceof

obj instanceof Fn checks whether Fn.prototype appears anywhere in obj's prototype chain:

JS instanceof.js
class Animal {}
class Dog extends Animal {}
const rex = new Dog();

console.log(rex instanceof Dog, rex instanceof Animal, rex instanceof Object);
console.log([] instanceof Array, {} instanceof Array);
Output
true true true
true false

Try it yourself

Try it yourself
Console output