Prototypal Inheritance

@BASarat

About me

Author Beginning Node.js

Beginning Node.js

About me

TypeScript <3

Top contributors TypeScript StackOverflow

DefinitelyTyped team members

Preview


function Animal(name) {
    this.name = name;
}
Animal.prototype.walk = function () { console.log(this.name + ' is walking') };

function Bird() {
    Animal.apply(this, arguments);
}
Bird.prototype.__proto__ = Animal.prototype;
Bird.prototype.fly = function () { console.log(this.name + ' is flying') };

var animal = new Animal('elephant');
animal.walk();

var bird = new Bird('crow');
bird.walk();
bird.fly();
  

> "elephant is walking" > "crow is walking" > "crow is flying"

this

Calling context

Global calling context example


function bar() {
  console.log(this);
}

bar();
          

> window

Member calling context example


var foo = {};

function bar() {
    console.log(this === foo);
}

foo.bar = bar;
foo.bar();
          

> true

Why calling context? (1)


var foo = {
    bar: 123,
    bas: function () {
        console.log('inside this.bar is: ' + this.bar);
    }
}

foo.bas();
        

> "inside this.bar is: 123"

Why calling context? (2)


function bar() {
    console.log('inside this.foo is: ' + this.foo);
}

var a = { foo: 123, bar: bar };
var b = { foo: 456, bar: bar };

a.bar();
b.bar();
      

> "inside this.foo is: 123" > "inside this.foo is: 456"

Why calling context? (3)


function bar() {
    console.log('inside this.foo is: ' + this.foo);
}

var a = { foo: 123 };
a.bar = bar;

a.bar();
    

> "inside this.foo is: 123"

__proto__

[[prototype]]

Same functions?


var foo = {};
var bar = {};
console.log(foo.toString == bar.toString);
  

> true

Same proto


var foo = {};
var bar = {};
log(foo.__proto__ == bar.__proto__);
  

> true

Same toString


var foo = {};
var bar = {};
log(foo.__proto__.toString == bar.__proto__.toString);
  

> true

__proto__ lookup


var foo = {};
console.log(foo.__proto__);
console.log(foo.__proto__.__proto__);

console.log('----');
foo.__proto__.bar = 123;
console.log(foo.bar);

console.log('----');
foo.bar = 456;
console.log(foo.bar);

console.log('----');
delete foo.bar;
console.log(foo.bar);
  

> {} > null > "----" > 123 > "----" > 456 > "----" > 123

__proto__ chain


var foo = {
    __proto__: {
        __proto__: {
            __proto__: {
                bar: 123
            }
        }
    }
};

console.log(foo.bar);
    

> 123

Extend the language


var objectProto = ({}).__proto__;
objectProto.log = function() {
    console.log(this);
};

var foo = {
    a: 123
};
var bar = {
    b: 456
};

foo.log();
bar.log();

> {"a":123} > {"b":456}

prototype

The effects of `new`

The prototype property


function Foo() { }
console.log(Foo.prototype);

// Has a default member
console.log(Foo.prototype.constructor === Foo);

> {} > true

Creates a new object


function Foo() {
}

// without the new operator
console.log(Foo());

// with the new operator
console.log(new Foo());

> undefined > {}

Effect on this


function Foo() {
    this.bar = 123;
    console.log('Is this global?: '+ (this == window));
}

// without the new operator
Foo();
console.log(window.bar);

// with the new operator
var newFoo = new Foo();
console.log(newFoo.bar);

> "Is this global?: true" > 123 > "Is this global?: false" > 123

prototype and __proto__


function Foo() { }

var foo = new Foo();

console.log(foo.__proto__ === Foo.prototype);

> true

prototype advantage


function Foo() {}
Foo.prototype.log = function() {
    console.log('log');
}

var a = new Foo();
var b = new Foo();

a.log();
b.log();

> "log" > "log"

Basic Class Structure


function Foo(val) {
    this.val = val;
}
Foo.prototype.log = function () {
    console.log(this.val);
}

var a = new Foo(1);
var b = new Foo(2);

a.log();
b.log();

> 1 > 2

Passing function references

bind

Bad calling context


var foo = {
    bar: 123,
    bas: function () {
        console.log('inside this.bar is: ' + this.bar);
    }
}

var bas = foo.bas;
bas();
  

> "inside this.bar is: undefined"

Verbose fix


var foo = {
    bar: 123,
    bas: function () {
        console.log('inside this.bar is: ' + this.bar);
    }
}

var bas = function () {
    return foo.bas();
};
bas();
  

> "inside this.bar is: 123"

bind


var foo = {
    bar: 123,
    bas: function () {
        console.log('inside this.bar is: ' + this.bar);
    }
}

var bas = foo.bas.bind(foo);
bas();
  

> "inside this.bar is: 123"

call

When you want to call the function with some this

bind


var foo = {
    bar: 123,
};

function bas() {
    console.log('inside this.bar is: ' + this.bar);
}

bas.bind(foo)();
  

> "inside this.bar is: 123"

call


var foo = {
    bar: 123,
};

function bas() {
    console.log('inside this.bar is: ' + this.bar);
}

bas.call(foo);
  

> "inside this.bar is: 123"

call with arguments


var foo = {
    bar: 123,
};

function bas(a, b) {
    console.log({bar:this.bar, a:a, b:b});
}

bas.call(foo, 1, 2);
  

> {"bar":123,"a":1,"b":2}

apply

Simple function interception

How to intercept?


var adder = {
    increment: 1,
    add: function (value) {
        return this.increment + value;
    }
}

// Want to intercept any call to add e.g.
console.log(adder.add(3));

> 4

Verbose


var adder = {
    increment: 1,
    add: function (value) {
        return this.increment + value;
    }
}

var oldAdd = adder.add;
adder.add = function(value){
    console.log('someone is calling with value: ' + value);
    return oldAdd.call(adder,value);
}

// Intercepted!
adder.add(3);

> "someone is calling with value: 3"

arguments


function foo(a,b,c){
  console.log(arguments);
}

foo('say', 'something');
foo('talk', 'to', 'me');

> {"0":"say","1":"something"} > {"0":"talk","1":"to","2":"me"}

apply


function foo(a,b,c){
    console.log(this);
    console.log({a:a,b:b,c:c});
}

foo.apply({bar:'123'}, ['talk', 'to', 'me']);

> {"bar":"123"} > {"a":"talk","b":"to","c":"me"}

intercept


var adder = {
    increment: 1,
    add: function (value) {
        return this.increment + value;
    }
}

var oldAdd = adder.add;
adder.add = function(value){
    console.log('someone is calling with value: ' + value);
    return oldAdd.apply(adder,arguments);
}

// Intercepted!
adder.add(3);
    

> "someone is calling with value: 3"

Chain

Creating a prototype heirarchy

Chain 101


function Animal() { }
Animal.prototype.walk = function () { console.log('walk') };

function Bird() { }
Bird.prototype.__proto__ = Animal.prototype;

var animal = new Animal();
animal.walk();

var bird = new Bird();
bird.walk();
  

> "walk" > "walk"

Add child functions


function Animal() { }
Animal.prototype.walk = function () { console.log('walk') };

function Bird() { }
Bird.prototype.__proto__ = Animal.prototype;
Bird.prototype.fly = function () { console.log('fly') };

var animal = new Animal();
animal.walk();

var bird = new Bird();
bird.walk();
bird.fly();
  

> "walk" > "walk" > "fly"

Member properties


function Animal(name) {
     this.name = name;
}
Animal.prototype.walk = function () { console.log(this.name + ' is walking') };

function Bird(name) {
}
Bird.prototype.__proto__ = Animal.prototype;
Bird.prototype.fly = function () { console.log(this.name + ' is flying') };

var animal = new Animal('elephant');
animal.walk();

var bird = new Bird('crow');
bird.walk();
bird.fly();
  

> "elephant is walking" > "undefined is walking" > "undefined is flying"

Member properties fixed (1)


function Animal(name) {
    this.name = name;
}
Animal.prototype.walk = function () { console.log(this.name + ' is walking') };

function Bird(name) {
    Animal.call(this, name);
}
Bird.prototype.__proto__ = Animal.prototype;
Bird.prototype.fly = function () { console.log(this.name + ' is flying') };

var animal = new Animal('elephant');
animal.walk();

var bird = new Bird('crow');
bird.walk();
bird.fly();
  

> "elephant is walking" > "crow is walking" > "crow is flying"

Member properties fixed (2)


function Animal(name) {
    this.name = name;
}
Animal.prototype.walk = function () { console.log(this.name + ' is walking') };

function Bird() {
    Animal.apply(this, arguments);
}
Bird.prototype.__proto__ = Animal.prototype;
Bird.prototype.fly = function () { console.log(this.name + ' is flying') };

var animal = new Animal('elephant');
animal.walk();

var bird = new Bird('crow');
bird.walk();
bird.fly();
  

> "elephant is walking" > "crow is walking" > "crow is flying"

Summary of pattern

  • Members go on
    this

  • Methods go on
    prototype

  • From Child constructor do
    Base.call(this,...args)

  • Child class chain via
    Child.prototype.__proto__ = Base.prototype

this Summary

Calling Context

great for mixins

makes prototype work

Constructor

new

Function members

Function reference?

bind

Calling a parent?

call

Intercepting?

apply

Reflection

Is or not?

Inherits or not?

Is or not?


function Animal() {}
Animal.prototype.walk = function () { console.log('walk') };

function Bird() {}
Bird.prototype.__proto__ = Animal.prototype;

var animal = new Animal();
var bird = new Bird();

console.log(animal.constructor == Animal);
console.log(bird.constructor == Bird);
        

> true > true

Inherits or not?

concept


function Animal() {}

function Bird() {}
Bird.prototype.__proto__ = Animal.prototype;

var animal = new Animal();

// how deep
console.log(animal.__proto__.__proto__.__proto__);

// tests
console.log(animal.__proto__ == Animal.prototype
    || animal.__proto__.__proto__ == Animal.prototype);
console.log(animal.__proto__ == Bird.prototype
    || animal.__proto__.__proto__ == Bird.prototype);
    
// Would be even longer for bird
        

> null > true > false

Inherits or not?

instanceof


function Animal() {}

function Bird() {}
Bird.prototype.__proto__ = Animal.prototype;

var animal = new Animal();
var bird = new Bird();

console.log(animal instanceof Animal);
console.log(animal instanceof Bird);

console.log(bird instanceof Bird);
console.log(bird instanceof Bird);
        

> true > false > true > true

Native Prototypes

and

Heirarchies

Function, Array, String, ...


function func(){}
var arr = [];
var str = '';
var bool = true;
var num = 123;

console.log(func.__proto__ == Function.prototype);
console.log(arr.__proto__ == Array.prototype);
console.log(str.__proto__ == String.prototype);
console.log(bool.__proto__ == Boolean.prototype);
console.log(num.__proto__ == Number.prototype);
        

> true > true > true > true > true

Object.prototype


var arr = [];

console.log(arr.__proto__ == Array.prototype);
console.log(arr.__proto__.__proto__ == Object.prototype);
console.log(arr instanceof Array);
console.log(arr instanceof Object);
        

> true > true > true > true

ES3,ES5,ES6

__proto__

new

Object.create

setPrototypeOf

Basic Example


function Animal() {}
Animal.prototype.walk = function () { console.log('walk') };

function Bird() {}
Bird.prototype.__proto__ = Animal.prototype;

var bird = new Bird();
bird.walk();

> "walk"

new


function Animal() {}
Animal.prototype.walk = function () { console.log('walk') };

function Bird() {}
function tmp(){}; tmp.prototype = Animal.prototype;
Bird.prototype = new tmp();

var bird = new Bird();
bird.walk();

> "walk"

new

constructor restored


function Animal() {}
Animal.prototype.walk = function () { console.log('walk') };

function Bird() {}
function tmp(){}; tmp.prototype = Animal.prototype;
Bird.prototype = new tmp();
Bird.prototype.constructor = Bird;

var bird = new Bird();
bird.walk();
    

> "walk"

Object.create


function Animal() {}
Animal.prototype.walk = function () { console.log('walk') };

function Bird() {}
Bird.prototype = Object.create(Animal.prototype);

var bird = new Bird();
bird.walk();
    

> "walk"

Object.create

constructor restored


function Animal() {}
Animal.prototype.walk = function () { console.log('walk') };

function Bird() {}
Bird.prototype = Object.create(Animal.prototype, {
  constructor: {
    value: Bird,
    enumerable: false,
    writable: true,
    configurable: true
  }
});

var bird = new Bird();
bird.walk();
    

> "walk"

Guidance

Use JavaScript?

inherits / src

Use TypeScript or CoffeScript?

Built in

ES6?

setPrototypeOf (But just use class)

THE END

BY Basarat Ali Syed (BAS)

@basarat