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
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);
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
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);
[ '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:
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);
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.
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:
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);
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
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());
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:
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);
true true true true false