Lesson 13-JavaScript Inheritance Patterns

JavaScript, as a prototype-based language, offers multiple ways to implement inheritance. From early prototype chain inheritance to modern ES6 class syntax, JavaScript’s inheritance mechanisms have evolved significantly. Below, I will detail the various inheritance methods in JavaScript and their characteristics.

Prototype Chain Inheritance

Prototype chain inheritance is the most fundamental inheritance method in JavaScript. It works by setting the child class’s prototype to an instance of the parent class.

function Parent() {
  this.name = 'Parent';
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child() {
  this.childName = 'Child';
}

// Key step: Set Child's prototype to a Parent instance
Child.prototype = new Parent();

const child1 = new Child();
child1.sayName(); // Output: Parent
console.log(child1.colors); // Output: ['red', 'blue', 'green']

// Issue 1: All child instances share parent instance properties
const child2 = new Child();
child1.colors.push('black');
console.log(child2.colors); // Output: ['red', 'blue', 'green', 'black']

// Issue 2: Cannot pass arguments to the parent constructor

Characteristics:

  • Simple to implement
  • All child instances share parent instance properties (problematic for reference types)
  • Cannot pass arguments to the parent constructor

Constructor Inheritance (Classical Inheritance)

Constructor inheritance achieves inheritance by calling the parent constructor within the child constructor using call or apply.

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, age) {
  // Key step: Call parent constructor
  Parent.call(this, name);
  this.age = age;
}

const child1 = new Child('Child1', 5);
const child2 = new Child('Child2', 6);

child1.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
console.log(child2.colors); // ['red', 'blue', 'green'] - Arrays not shared

// Issue: Cannot inherit parent prototype methods
console.log(child1.sayName); // undefined

Characteristics:

  • Avoids sharing of reference type properties
  • Allows passing arguments to the parent constructor
  • Cannot inherit methods from the parent prototype

Combination Inheritance (Most Common)

Combination inheritance merges the strengths of prototype chain and constructor inheritance, making it the most commonly used approach.

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, age) {
  // Inherit instance properties (constructor inheritance)
  Parent.call(this, name);
  this.age = age;
}

// Inherit prototype methods (prototype chain inheritance)
Child.prototype = new Parent();
Child.prototype.constructor = Child; // Fix constructor reference
Child.prototype.sayAge = function() {
  console.log(this.age);
};

const child1 = new Child('Child1', 5);
const child2 = new Child('Child2', 6);

child1.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
console.log(child2.colors); // ['red', 'blue', 'green'] - Arrays not shared

child1.sayName(); // Child1
child1.sayAge(); // 5

// Issue: Calls parent constructor twice (once in Child.prototype = new Parent(), once in Parent.call(this, name))

Characteristics:

  • Combines advantages of prototype chain and constructor inheritance
  • Most commonly used inheritance method
  • Calls the parent constructor twice (performance issue)

Prototypal Inheritance

Prototypal inheritance is similar to ES5’s Object.create() method, creating a new object based on an existing one.

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

const parent = {
  name: 'Parent',
  colors: ['red', 'blue', 'green'],
  sayName: function() {
    console.log(this.name);
  }
};

const child1 = object(parent);
child1.name = 'Child1';
child1.sayName(); // Child1

const child2 = object(parent);
child2.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black'] - Shared reference type properties

Characteristics:

  • Similar to ES5’s Object.create()
  • Suitable for scenarios not requiring a separate constructor
  • Reference type properties are shared among instances

Parasitic Inheritance

Parasitic inheritance builds on prototypal inheritance by enhancing the created object.

function createAnother(original) {
  const clone = object(original); // Create object using prototypal inheritance
  clone.sayHi = function() { // Enhance object
    console.log('hi');
  };
  return clone;
}

const parent = {
  name: 'Parent',
  colors: ['red', 'blue', 'green']
};

const child = createAnother(parent);
child.sayHi(); // hi
child.sayName(); // Parent

Characteristics:

  • Enhances objects based on prototypal inheritance
  • Suitable for focusing on objects rather than custom types or constructors
  • Reference type properties are shared, similar to prototypal inheritance

Parasitic Combination Inheritance (Most Ideal)

Parasitic combination inheritance is the most ideal approach for reference types, avoiding the issue of calling the parent constructor twice.

function inheritPrototype(child, parent) {
  const prototype = Object.create(parent.prototype); // Create a copy of parent prototype
  prototype.constructor = child; // Fix constructor reference
  child.prototype = prototype; // Assign copy to child prototype
}

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, age) {
  Parent.call(this, name); // Call parent constructor once
  this.age = age;
}

// Key step: Parasitic combination inheritance
inheritPrototype(Child, Parent);

Child.prototype.sayAge = function() {
  console.log(this.age);
};

const child1 = new Child('Child1', 5);
child1.sayName(); // Child1
child1.sayAge(); // 5

const child2 = new Child('Child2', 6);
console.log(child1.colors); // ['red', 'blue', 'green']
child1.colors.push('black');
console.log(child2.colors); // ['red', 'blue', 'green'] - Not shared

Characteristics:

  • Most ideal inheritance method
  • Calls parent constructor only once
  • Maintains the prototype chain
  • Avoids drawbacks of combination inheritance

ES6 Class Syntax Sugar

ES6 introduced the class keyword, offering a cleaner syntax for object-oriented programming. It is syntactic sugar over prototype-based inheritance but provides clearer, more traditional object-oriented syntax.

class Parent {
  constructor(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
  }

  sayName() {
    console.log(this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // Call parent constructor
    this.age = age;
  }

  sayAge() {
    console.log(this.age);
  }
}

const child1 = new Child('Child1', 5);
child1.sayName(); // Child1
child1.sayAge(); // 5

const child2 = new Child('Child2', 6);
console.log(child1.colors); // ['red', 'blue', 'green']
child1.colors.push('black');
console.log(child2.colors); // ['red', 'blue', 'green'] - Not shared

Characteristics:

  • Syntactic sugar, still based on prototypes
  • Clearer syntax, closer to traditional object-oriented languages
  • Built-in implementation of parasitic combination inheritance
  • Provides super keyword to access parent class

Comparison of Inheritance Methods

Inheritance MethodAdvantagesDisadvantages
Prototype ChainSimple to implementShared reference type properties, cannot pass arguments
ConstructorAvoids shared references, supports argumentsCannot inherit prototype methods
CombinationCombines benefits of bothCalls parent constructor twice
PrototypalSimple, based on existing objectsShared reference type properties
ParasiticEnhances objectsShared reference type properties
Parasitic CombinationMost ideal, calls parent constructor onceSlightly complex implementation
ES6 ClassClean syntax, built-in best practicesRequires ES6 support

Share your love