Prototypal Inheritance

Steve Venzerul

Good Ol' Classical Inheritance

//Fictional language (probably C++)
class Animal {
  public:
    Animal();
    void walk();
    void makeNoise();
    void eat();
    void poop();
};
class Duck : public Animal {
  public:
    Duck() : Animal();
    void quack();
}

Object

Object

Duck duffy = new Duck();
duffy.quack();

Instance

Proto-not-so-typical

Functions instead of Objects

Function

function Person() {
  this.name = 'Anon';
}

Function

function Manager(name) {
  Person.call(this);
  this.name = name;
}
Manager.prototype.askYouToComeInOnSatureday = function() {
  Person.prototype.sayHello.call(this);
  var msg = "Yeeeeeeeeah, I\'m gonna need" +
            "you to go ahead and come in on Satureday.";
  console.log(msg);
};

Instance

var EvilManager = new Manager('Lumberg');
EvilManager.askYouToComeInOnSatureday()

Not exactly inheritance

function Person() {
  this.name = 'Anonymous';
}
Person.prototype.sayHello = function() {
  console.log('Howdy, my name\'s', this.name);
};


function Man() {
  Person.call(this);
  this.name = 'Timmy';
}
Man.prototype = new Person();
//Alternative: Man.prototype = Object.create(Person.prototype);
Man.prototype.constructor = Man;

var t = new Man();

t.sayHello();
//Howdy, my name's Timmy

console.log(t instanceof Man);
//true
console.log(t instanceof Person);
//true

//Delegation
console.log(Object.getPrototypeOf(t) === Man.prototype);

A helping hand

//ES5 Inheritance helper.
//Avoids invoking the parent constructor function.
function inherits(child, parent) {
  var Temp = function(){};
  Temp.prototype = parent.prototype;
  child.prototype = new Temp();
  child.prototype.constructor = child;
}
//Greatly simplified in ES6
function inherits(child, parent) {
  Object.setPrototypeOf(child.prototype, parent.prototype);
}

Something better

Why are we messing with the constructor?

//ES5 Inheritance helper.
//Avoids invoking the parent constructor function.
function inherits(child, parent) {
  var Temp = function(){};
  Temp.prototype = parent.prototype;
  child.prototype = new Temp();

  //---------------------------
  child.prototype.constructor = child;
  //---------------------------
}

The thing about `instanceof`

//JS's instanceof operator is kinda dumb. It can be easily
//fooled by futzing about with the prototype chain.
//To make sure everything is correct we fix the constructor.
child.prototype.constructor = child;

//Without that line the following will yield false
Child instanceof Parent
//false <-- see, I told you.

Statically speaking

function ICanHazStatics() {}

ICanHazStatics.giveCheese = function() {
  console.log('Got cheese!');
};
ICanHazStatics.eatCheese = function() {
  console.log('nom nom nom');
};

//Not to be confused with prototype methods
var chaz = new ICanHazStatics();
chaz.giveCheese();
//TypeError: giveCheese is not a function

ICanHazStatics.giveCheese();
//Got cheese!

The magic of Object.create()

function SuperCar() {}

function Koenigsegg() {
    SuperCar.call(this);
}

Koenigsegg.prototype = Object.create(SuperCar.prototype);
Koenigsegg.prototype.constructor = Koenigsegg;

When the curtain falls

//Object.create shim, courtesy of the Crockford.
function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

//Object.create ES6-sty
Object.create = function(proto) {
  var obj = {};
  Object.setPrototypeOf(obj, proto);
  return obj;
};

__proto__ - The hidden treasure

Hands off the __proto__

function Cheese() {
  this.color = 'Yellow';
}
Cheese.prototype.isSmelly = function() {
  console.log('Oh god yes!');
};


function Cheddar() {
  this.color = 'Orange-y';
}
Cheddar.prototype = Object.create(Cheese.prototype);
Cheddar.prototype.constructor = Cheddar;

var slice = new Cheddar();  

//Some truths
slice.__proto__ === Cheddar.prototype;
//true
Cheddar.prototype.__proto__ === Cheese.prototype;
//true

__proto__ is not standard. Except, ES2015 made it standard for backwards compatibility because all current major browsers support and they didn't want to 'break the web'. However, they _strongly_ discourage the public from using this property. What they're saying is -- here's a gun, but don't shoot anyone. 

//ES2015 introduces Object.getPrototypeOf() and Object.setPrototypeOf()
var slice = new Cheddar();

Object.getPrototypeOf(slice) === Cheddar.prototype;

var slice = {};

Object.setPrototypeOf(slice, Cheddar.prototype);

slice.isSmelly();
//Oh god yes!

console.log(slice.color);
//undefined

//Woah, woah, woah! Why is the color undefined? 
//Because the constructor function Cheddar() was never
//run on the new slice object we just created.

The path of the righteous

Inheritance sugar in your Java-script

//ES6
class PartyAnimal {
  constructor(name) {
    this.name = name;
  }
  makeNoise(moreNoise) {
    console.log('Paaaaartttyyyy!,', moreNoise);
  }
}

class SpikyHairedGentleman extends PartyAnimal {
  constructor(name, lifts) {
    super(name);
    this.lifts = lifts;
  }
  makeNoise() {
    super.makeNoise('Where\'s the vodka at bro?');
  }
  doesLift() {
    if (this.lifts) {
      console.log('Of course bro, never skip leg day.');
    } else {
      console.log('Planning on it bro!');
    }
  }
}

let Fabio = new SpikyHairedGentleman('Fabio', true);
Fabio.makeNoise();
//Paaaaartttyyyy!, Where's the vodka at bro?
Fabio.doesLift();
//Of course bro, never skip leg day.

ES5, leading cause of Repetitive Strain Injury

function ParentConstructor() {}

function SomeConstructor() {
    ParentConstructor.call(this);
}

SomeConstructor.prototype.method = function() {
    SuperClass.prototype.method.apply(this, args);
}

Doctor ES2015

class SomeConstructor extends ParentConstructor {
  //Not needed. Done automatically for you by the engine.
  constructor(...args) {
    super(...args);
  }
  inheritedMethod() {
    super.inheritedMethod();
  }
}

//But Steve, what happens if I call `sm.inheritedMethod.call(this)`? 
//The answer is Zalgo, of course.
//In ES6 methods that contain super() references have something called a 
//HomeObject which essentially binds them to the object they were declared
//in. This effectively means you can't .call() or .apply() them. Well, you 
//can, but you won't have the 'this' you're expecting, leading to serious
//and persistent programmer tears.
(new SomeConstrctor()).inheritedMethod.call(this); //No bueno

Subclassing Native Types

class ShinyThings extends Array {
    getShiny() {
      return this.filter(thing => { 
        return thing.shines; 
      });
    }
}
let a = new ShinyThings({shines: true}, {shines: false}, {shines: true}, {shines: true});
console.log(a.getShiny());
//[{shines: true}, {shines: true}, {shines: true}]
console.log(a.length);
//4
class UniqueObject extends Object {
  constructor(...args) {
    super(...args);
    this.id = this._getUnique();
  }

  _getUnique(len = 16) {
    let set = 'abcdefghijklmnopqestuvwxyz1234567890',
      id = '';
    
    for(let i = 0; i < len; i++) {
      id += set[Math.floor(Math.random() * len)];
    }
  
    return id;
  }
}
let ub = new UniqueObject();
console.log(ub);
//UniqueObject { id: '1f8v9h24v8h2358' }

Snowflake Object

Subclassing String - Attack of the V8 Bug

//Wanna subclass the String object in an easy and fun way? NO SOUP FOR YOU!
class Str extends String {
  capitalize() {
    return `${this.slice(0, 1).toUpperCase()}${this.slice(1)}`;
  }
}

s.capitalize()
//TypeError: s.capitalize is not a function
class Str extends String {
  constructor(...args) {
    super(...args);
    Object.setPrototypeOf(this, new.target.prototype);
  }

  capitalize() {
    return `${this.slice(0, 1).toUpperCase()}${this.slice(1)}`;
  }
}

var s = new Str('yarn');
console.log(s.constructor);
//[Function: Str]
console.log(Object.getPrototypeOf(s));
//Str {}
console.log(s.capitalize());
//Yarn

setPrototypeOf() saving the day

Static Recall

class UniqueObject extends Object {
  constructor() {
    super();
    this.id = UniqueObject._getUnique();
  }

  static _getUnique(len = 16) {
    let set = 'abcdefghijklmnopqestuvwxyz1234567890',
      id = '';
    
    for(let i = 0; i < len; i++) {
      id += set[Math.floor(Math.random() * len)];
    }
  
    return id;
  }
}

let ub = new UniqueObject();
console.log(ub);
//UniqueObject { id: '3jfu6kb8tud7e9r5' }

console.log(UniqueObject._getUnique());
//9fkv85m90d8j3y47

//Equivalent ES5
UniqueObject.getUnique = function() {
    //yada yada 
}

Questions?

Prototypal Inheritance

By signupskm

Prototypal Inheritance

  • 1,527