Classical Inheritance in Javascript

2018 Edition

Who is Thanasis

Professional

Community

Open Source

  • Eng Mgr at Waldo
  • CTO at QallOut
  • CTO at Insight Replay
  • Founder
  • Over 40 NPM packages
  • Contributor to major Node.js packages
  • Avid OSS author
  • Software Engineer, CTO, founder
  • Recently moved to London from Greece
  • Available for hire

Inheritance in Javascript

  • Lots of ways
  • It's complicated
  • We'll talk about Classical / Prototypical

Why Classical / Prototypical?

Why Classical / Prototypical?

var Animal = function() {};

var cat = new Animal();

cat instanceof Animal; // true
  • Forces use of a Constructor
  • Uses the "new" keyword
  • "instanceof" works

Utilizes all the language features provided for inheritance

Why Classical / Prototypical?

class Animal {}

var cat = new Animal();

cat instanceof Animal; // true

ES6 "class" keyword is prototypical inheritance

Why Classical / Prototypical?

var util = requite('util');

// Parent Ctor
var Animal = function() {};

var Cat = function() {
    Animal.call(this);
};
util.inherits(Cat, Animal);

For ES5 Node.js Provides helpers built in the language (versions 6 and bellow)

Why Classical / Prototypical?

Alternatives to Classical Inheritance

  • A bunch of variations on how to do Classical (Object.create, Object.assign)
  • Composition
  • Object literals and the rest...

Why Classical / Prototypical?

Closing points onto WHY

  • It is the language's intended way for inheritance.
  • Generally accepted as the way to do inheritance in Javascript.
  • Signal / Noise ratio in JS land is a problem, stay confident & focused.

How to Inherit

// Parent Ctor
class AnimalES6 {
    constructor(name) {
        this.name = name;
    }
}

class Cat extends AnimalES6 {
    constructor(color) {
        super('cat'); //call the parent method with super
        this.color = color;
    }
}

ES6 and beyond

How to Inherit

var util = require('util');

// Parent Ctor
var Animal = function() {};

var Cat = function() {
    Animal.call(this);
};
util.inherits(Cat, Animal);

ES5 in Node.js

How to Inherit

var inherits = function(ChildCtor, ParentCtor) {
  ChildCtor.prototype = Object.create(ParentCtor.prototype);
  ChildCtor.prototype.constructor = ParentCtor;
};

// Parent Ctor
var Animal = function() {};

var Cat = function() {
    Animal.call(this);
};
inherits(Cat, Animal);

How "util.inherits()" works

How to Inherit

var inherits = function(ChildCtor, ParentCtor) {
  function TempCtor() {};
  TempCtor.prototype = ParentCtor.prototype;
  ChildCtor.prototype = new TempCtor();

  ChildCtor.prototype.constructor = ChildCtor;
};

// Parent Ctor
var Animal = function() {};

var Cat = function() {
    Animal.call(this);
};
inherits(Cat, Animal);

Vanilla Javascript ES3

What did just happen?

... lets talk about the prototype

The Javascript Prototype

Javascript is a prototypical language

  • All Objects have a prototype
  • Everything in JS is an Object
    • var str = new String();
    • var bool = new Boolean();
    • var num = new Number();
  • The Prototype is the blueprint for creating objects and instances
  • The Instances or Objects do not have a prototype
  • Functions have a prototype

The Javascript Prototype

The Prototype is the blueprint for creating objects and instances

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

Animal.prototype.getName = function() {
  return this.name;
};

var pony = new Animal('pony');

pony.getName(); // "pony"

The instance

Invokes the Constructor

The Constructor

Instance local variable

A method

The Javascript Prototype

Going down the rabbit hole...

var fn = function() {}

fn.prototype
// fn {}
//     -> constructor: function()
//     -> __proto__: Object

All prototypes have at least two properties:

  • constructor: A Function that gets invoked when constructing the instance
  • __proto__: A reference to the parent prototype

The Javascript Prototype

Going downer the rabbit hole...

var str = 'a string';

str.__proto__;

// Outputs the Object that was used to construct
// the "str" instance:
// String {length: 0, [[PrimitiveValue]]: ""}

All variables of any type in Javascript have the __proto__ property!

The Javascript Prototype

All variables of any type in Javascript have the __proto__ property!

Remember:

  • __proto__ is a reference
  • Points to the Object that constructed the instance
  • Instances using the "new" keyword have a __proto__ that points to the Ctor

Inheritance Explained

var inherits = function(ChildCtor, ParentCtor) {
  function TempCtor() {};
  TempCtor.prototype = ParentCtor.prototype;
  ChildCtor.prototype = new TempCtor();

  ChildCtor.prototype.constructor = ChildCtor;
};

Inheritance Explained

var inherits = function(ChildCtor, ParentCtor) {
  function TempCtor() {};
  TempCtor.prototype = ParentCtor.prototype;
  ChildCtor.prototype = new TempCtor();

};
  ChildCtor.prototype.constructor = ChildCtor;

Create a temporary Constructor to copy the Parent Prototype on.

Inheritance Explained

var inherits = function(ChildCtor, ParentCtor) {
  function TempCtor() {};
  TempCtor.prototype = ParentCtor.prototype;
  ChildCtor.prototype = new TempCtor();

};
  ChildCtor.prototype.constructor = ChildCtor;

Copy the Parent's Prototype to the temporary Constructor's prototype

Inheritance Explained

var inherits = function(ChildCtor, ParentCtor) {
  function TempCtor() {};
  TempCtor.prototype = ParentCtor.prototype;
  ChildCtor.prototype = new TempCtor();

};
  ChildCtor.prototype.constructor = ChildCtor;

Instanciate the temporary Ctor and assign it by overwriting the Child's prototype.

 

This is where inheritance happens.

Inheritance Explained

var inherits = function(ChildCtor, ParentCtor) {
  function TempCtor() {};
  TempCtor.prototype = ParentCtor.prototype;
  ChildCtor.prototype = new TempCtor();

};
  ChildCtor.prototype.constructor = ChildCtor;

Let's break this out, too much happened here:

  • "var inst = new TempCtor()" we created an instance

  • The instance has a __proto__ referencing the TempCtor

  • Which in our case is the Parent's Prototype.

Inheritance Explained

var inherits = function(ChildCtor, ParentCtor) {
  function TempCtor() {};
  TempCtor.prototype = ParentCtor.prototype;
  ChildCtor.prototype = new TempCtor();

};
  ChildCtor.prototype.constructor = ChildCtor;
  • We overwrite the prototype's special property "constructor"

  • Using the actual Child Ctor

  • Remember, the Ctor is a function

Inheritance Explained

var inherits = function(ChildCtor, ParentCtor) {
  function TempCtor() {};
  TempCtor.prototype = ParentCtor.prototype;
  ChildCtor.prototype = new TempCtor();

};
  ChildCtor.prototype.constructor = ChildCtor;
  • So essentially we discard the default Child's prototype

  • Overwriting it with the instance of the Parent Ctor

Inheritance Explained

var inherits = function(ChildCtor, ParentCtor) {
    ClildCtor.prototype = ParentCtor.prototype;
};
  • This isn't a "copy", this is "assignment by reference"

  • Every change you perform on the Child's prototype will directly change the Parent's prototype, they are the same

Why not:

Inheritance Explained

var inherits = function(ChildCtor, ParentCtor) {
    ClildCtor.prototype = new ParentCtor()
};
  • This is a "copy"

  • BUT you are also invoking the Parent's Ctor which can have severe side-effects

Why not:

We are not finished yet!

The Inheritance

So...

We copied the Parent's prototype to the Child...

... now we need to make sure that all the Constructors from our Parents will be invoked!

... in the right Scope!

The Inheritance

The Scope....

The Inheritance

var util = require('util');

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

Animal.prototype.getName = function() {
  return this.name;
};

// Inherits from Animal
var Cat = function(name, color) {
  Animal.call(this, name);

  this.color = color;
};
util.inherits(Cat, Animal);

Cat.prototype.getColor = function() {
  return this.color;
};

That's the key Part right there!

The Scope

99% of your problems will come from Scope

SO PAY ATTENTION

The Scope

Scope refers to where variables and functions are accessible, and in what context it is being executed.

In the case of an Instance, scope is critical in maintaining access to methods and local properties

The Scope

Base Definition

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

Animal.prototype.getName = function() {
  return this.name;
};

The Scope

Retains Scope - Vanilla

var animal = new Animal('cat');

animal.getName();
// "cat"

Looses Scope

var animal = new Animal('cat');

var getName = animal.getName;
getName();
// undefined

Retains Scope - bind

var animal = new Animal('cat');

var getName = animal.getName.bind(animal);
getName();
// "cat"

The Scope

Retains Scope - call

var animal = new Animal('cat');

var getName = animal.getName;
getName.call(animal);
// "cat"

Retains Scope - apply

var animal = new Animal('cat');

var getName = animal.getName;
getName.apply(animal);
// "cat"

The Scope

In Practice...

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

  // this will fail
  this.on('some event', this.doSomething);

  // this is ok
  this.on('some event', this.doSomething.bind(this));
};

var animal = new Animal('cat');

// this will fail
onEvent(animal.onEventHandler);

// this is ok
onEvent(animal.onEventHandler.bind(animal));

// With Arrow function
onEvent(() => animal.onEventHandler);

So, back to inheritance

Inheritance Ctor

// Inherits from Animal
var Cat = function(name, color) {
  Animal.call(this, name);

  this.color = color;
};
util.inherits(Cat, Animal);

Cat.prototype.getColor = function() {
  return this.color;
};
  1. First thing you do in your Ctor is to call your Parent's Ctor using your own, new, context (scope)
  2. Then, right after the Ctor, invoke the inherits function
  3. Afterwards define your methods. Can even overwrite the parent's

1

3

2

Inheritance Ctor

// Inherits from Animal
Class Cat extends Animal {
    constructor (name, color) {
        super(name);

        this.color = color;
    }

    getColor() {
        return this.color;
    }
}
  1. First thing you do in your Ctor is to call your Parent's Ctor with super
  2. Afterwards define your methods. Can even overwrite the parent's

1

2

From Node 8

The Prototype Chain

The Prototype Chain

  • The more you extend a Ctor creating childs the longer the chain gets
  • Js will lookup serially for a method all the way up to the last prototype
  • This can result in performance hits in some particular cases

Common Gotchas

  • Only define methods on the prototype
  • Everything else on the Ctor
var Animal = function(name) {
  this.name = name;
};

// Never do this
Animal.prototype.name = '';

Common Gotchas

  • Never invoke the inheritance method after your methods
var util = require('util');

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

// Inherits from Animal
var Cat = function(name, color) {
  Animal.call(this, name);

  this.color = color;
};

Cat.prototype.getColor = function() {
  return this.color;
};

// Will overwrite and DELETE getColor()
util.inherits(Cat, Animal);

Common Gotchas

Scope on callbacks

Animal.prototype.getName = function(cb) {
  this.db.getName(function(name) {
   // NEW SCOPE HERE, "this" REFERS TO
   // THIS FUNCTION'S CONTEXT 
   cb(name);
  });
};
  • Use .bind(this) at end of the anonymous function expression used as a callback
  • Use the Arrow Function Expression ( fat arrow => )

Trivia & Best Practises

  • We call Constructors "Constructors" and not "Classes" because they do not behave like Classes
  • They resemble a Class, thus the term "Classical Inheritance"
  • But in reality, this is "Prototypical Inheritance"
  • We signify a Ctor by capitalizing the first letter
var Animal = function() {}

Trivia & Best Practises

  • Use the constructor strictly to construct the instance
  • Asynchronous operations in Ctors are forbidden!
  • Define any and all properties in the constructor
  • Use null to initialize properties with no value
  • Never return any value from the Ctor
    • It will screw up everything
var Animal = function() {
  this.name = null;
  this.color = null;
}

Trivia & Best Practises

  • You may define a method on the Ctor directly, that is considered a Static function and will not be inherited
var Animal = function() {
  this.name = null;
  this.color = null;
}

Animal.staticFn = function() {
  // Will not be inherited
};

Thank you

Thanasis Polychronakis

@thanpolas

https://speakerdeck.com/thanpolas

Questions

Thanasis Polychronakis

@thanpolas

https://speakerdeck.com/thanpolas

¿