Prototypal Inheritance – @BASarat – About me



Prototypal Inheritance – @BASarat – About me

1 1


this-and-prototype

Presentation explaining this and prototype behavior in JavaScript

On Github basarat / this-and-prototype

Prototypal Inheritance

@BASarat

About me

Author 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"

When you do not care about the base class constructor arguments

this

Calling context

Depends not upon the function, but upon how you call the function. Think of it as an arugment that is implicitly passed to the function by the runtime.

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

Are these the same functions? Where are they coming from? (Object.__proto__.toString)

Same proto

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

> true

Share the proto

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

Since proto is shared don't do this!

__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}

Now you shouldn't do this deep in your library or you are bound to break other libraries and possibly future JavaScript

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

Non enumerated constructor property

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"

Provides a standard way of creating shared functions

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

Each gets its own `this` and proto (pointing to prototype) is shared we get an efficient class structure. The `this` inside the prototype members is depended upon the calling context

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"

We use bind because we don't want to mutate the object or the function

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}

Call is needed to create a prototype chain and we will see it next

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"}

Array like object. Independent of any explicit arguments that you pass in.

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"}

Similar to call except second argument is an array that gets destructured into parameters. First argument is "this" same as call

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"

Just apply the arguments on the old function, and guaranteed to not break any code

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"

Does anybody see the error in this?

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"

When you do not care about the base class constructor arguments

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

Since proto points to prototype and prototype and member constructor

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"

Anything you already know that can take a prototype and move it to __proto__?

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"

Deletes our reference to constructor

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