JavaScript prototypes and class inheritance

See that classes are built on prototype chains, and extend one constructor from another.

JavaScript
function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  return this.name + " makes a sound";
};

const rex = new Animal("Rex");
console.log(rex.speak(), Object.getPrototypeOf(rex) === Animal.prototype);

class Dog extends Animal {
  speak() {
    return super.speak() + " (woof)";
  }
}

const fido = new Dog("Fido");
console.log(fido.speak());
console.log(fido instanceof Dog, fido instanceof Animal, Object.hasOwn(fido, "speak"));
Output
Rex makes a sound true
Fido makes a sound (woof)
true true false

Explanation

Every JavaScript object has a hidden link to a prototype object. When a property is not found on the object itself, the engine looks it up along the chain of prototypes. A constructor function sets up new objects with this, and methods placed on Constructor.prototype are shared by all instances instead of being copied.

The class syntax is a cleaner way to write the same thing. class Dog extends Animal sets Dog.prototype's prototype to Animal.prototype, and super.method() calls the parent's version. instanceof checks the prototype chain. A method defined on the prototype is not an own property of the instance, which Object.hasOwn shows.

Explore the dry run
Guided walkthroughReady
1function Animal(name) {
2 this.name = name;
3}
4Animal.prototype.speak = function () {
5 return this.name + " makes a sound";
6};
7
8const rex = new Animal("Rex");
9console.log(rex.speak(), Object.getPrototypeOf(rex) === Animal.prototype);
10
11class Dog extends Animal {
12 speak() {
13 return super.speak() + " (woof)";
14 }
15}
16
17const fido = new Dog("Fido");
18console.log(fido.speak());
19console.log(fido instanceof Dog, fido instanceof Animal, Object.hasOwn(fido, "speak"));

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
One shared function on the prototype, not one copy per animal.—
—
new creates an object whose prototype is Animal.prototype.—
—
speak is found on the prototype, and the link is confirmed.—
Rex makes a sound true
Dog.prototype inherits from Animal.prototype.—
Rex makes a sound true
super calls the parent's method and the child adds to the result.—
Rex makes a sound true
Dog's speak runs first and calls Animal's.—
Rex makes a sound true
Fido makes a sound (woof)
fido is a Dog and an Animal. The speak method lives on the prototype, not on fido.—
Rex makes a sound true
Fido makes a sound (woof)
true true false

Cost and common mistakes

Property lookup walks the prototype chain: O(depth of the chain).

  • Calling a constructor function without new sets this to undefined (in strict mode) or the global object, so use classes, which refuse to be called without new.
  • Changing a prototype affects every instance, including existing ones. Do not modify built-in prototypes such as Array.prototype.
  • Extending built-ins and deep inheritance hierarchies are usually worse than composition.

Try it yourself

What does rex.hasOwnProperty("name") return?

Reveal answer and reasoning

true, because name was assigned on the object itself by the constructor.

Further reading