JavaScript

Methods of Inheritance

Jonathan Kemp

@jonkemp

Jonathan Kemp

Senior Front End Engineer

@ Scripps Networks

Knoxville, TN

The Kemp Family

Follow ALong:

https://slides.com/jonkemp/methods-of-inheritance/live

What is the Nature of

JavaScript Inheritance?

Problems with Javascript Inheritance

  1. Expressing classes and inheritance in pure JavaScript is difficult.
  2. Trying to mimic "classical" inheritance in JavaScript, we miss a powerful feature that was built into the language from the start.
  3. JavaScript's natural inheritance behavior is often misunderstood.

Objects

simple types of JavaScript

numbers, strings, booleans, null, and undefined

All other values are objects

Numbers, strings, and booleans are immutable

Objects are mutable keyed collections

arrays = objects

functions = objects

regular expressions = objects objects = objects

Primitive Wrappers

With the exception of `null` and `undefined`, the other primitive value types (number, string and boolean) have primitive wrapper objects.

The wrapper objects have some useful properties and methods, as well as a prototype.

// avoid these:
var s = new String("my string");
var n = new Number(101);
var b = new Boolean(true);

// better and simpler:
var s = "my string";
var n = 101;
var b = true;

The Object constructor is also a primitive wrapper object with static methods and a prototype.

// Examples of static methods

Object.assign()
Object.create()
Object.defineProperties()

// Examples of methods on the prototype

Object.prototype.hasOwnProperty()
Object.prototype.toString()
Object.prototype.valueOf()

For full documentation, see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object

Object Creation Patterns

Literals and Constructors

// Object contructor
var myObj = new Object();

// Object Literal
var myObj = {};

Object Literals

  • Shorter to type
  • Simpler
  • Preferred over constructors

Prototype

Every object is linked to a prototype object

FROM WHICH IT CAN "INHERIT" PROPERTIES

When you make a new object,

you can select the object

To use as ITS PROTOTYPE

// Define a constructor and augment its prototype
var Mammal = function (name) {
    this.name = name;
};

Mammal.prototype.get_name = function () { 
    return this.name;
};

Mammal.prototype.says = function () { 
    return this.saying || '';
};


var Cat = function (name) {
    this.name = name;
    this.saying = 'meow';
};

// and replacing Cat.prototype with a new instance of Mammal
Cat.prototype = new Mammal();

Cat.prototype.get_name = function () {
    return this.says() + ' ' + this.name + ' ' + this.says();
};

var myCat = new Cat('Henrietta');

var says = myCat.says(); // Mammal.prototype

var name = myCat.get_name(); // Cat.prototype

property retrieval

Prototype Chain

  1. Try to retrieve a property value from an object, and it doesn't exist.
  2. JavaScript attempts to retrieve the property value from the prototype object.

Understanding delegation

Despite the borrowed implications of the common name "prototypal inheritance", JavaScript's mechanism works quite differently.

dynamic

If we add a new property to a prototype, that property will immediately be visible in all of the objects that are based on that prototype.

augmenting basic types

Because of the dynamic nature of JavaScript’s prototypal inheritance, all values are immediately endowed with the new methods, even values that were created before the methods were created.

Inheritance

Inheritance

is a Form of Code Reuse

In programming, the idea of "inheritance" is most closely associated with the idea of "copying" from parent to child.

classical inheritance

In classical languages, objects are instances of classes, and a class can inherit from another class.

classically inspired

JavaScript's constructor functions are reminiscent of the classical languages.

Constructor pattern

PSEUDOCLASSICAL Inheritance

Constructor Functions

  1. Create a function which we use as a "constructor"
  2. Call that function with `new` so that we can "instantiate" our "class"
  3. Extend with subsequent .prototype additions.
// Define a constructor and augment its prototype
var Mammal = function (name) {
    this.name = name;
};

Mammal.prototype.get_name = function () { 
    return this.name;
};

Mammal.prototype.says = function () { 
    return this.saying || '';
};

// Make an instance
var myMammal = new Mammal('Herb the Mammal');

var name = myMammal.get_name(); // 'Herb the Mammal'
// Inherits from Mammal by defining its constructor function
var Cat = function (name) {
    this.name = name;
    this.saying = 'meow';
};

// and replacing Cat.prototype with a new instance of Mammal
Cat.prototype = new Mammal();

// Augment the new prototype with
// purr and get_name methods.
Cat.prototype.purr = function (n) {
    var i, s = '';
    for (i = 0; i < n; i += 1) {
        if (s) {
            s += '-';
        }
        s += 'r';
    }
    return s;
};

Cat.prototype.get_name = function () {
    return this.says() + ' ' + this.name + ' ' + this.says();
};

var myCat = new Cat('Henrietta');
var says = myCat.says(); // 'meow'
var purr = myCat.purr(5); // 'r-r-r-r-r' 
var name = myCat.get_name(); // 'meow Henrietta meow'

calling a constructor

  1. Creates an empty object
  2. Sets the prototype of this object to the prototype property of the constructor
  3. Calls the constructor function with `this` pointing to the newly-created object
  4. Returns the object

The new keyword and the constructor

  • Built into JavaScript to make it look familiar to people trained in class-based programming
  • No privacy; all properties are public
  • No access to super methods

Problems with the constructor pattern

  • Did not appeal to the classical crowd
  • Obscured JavaScript’s true prototypal nature
  • Many don't know how to use the language effectively

More Problems with the constructor

  • ​In ES3, if you return an arbitrary object from a constructor function, the `this` keyword will no longer be bound to the new object instance in the constructor.
  • This was fixed when using strict mode in ES5.
  • If a caller forgets `new` and you’re not using strict mode or ES6 classes, anything you assign to `this` will pollute the global namespace.

ES2015 Classes

class Mammal {
    constructor(name) {
        this.name = name;
    }

    get_name() { 
        return this.name;
    }

    says() { 
        return this.saying || '';
    }
}

const myMammal = new Mammal('Herb the Mammal');

const name = myMammal.get_name(); // 'Herb the Mammal'
class Cat extends Mammal {
    constructor(name) {
        this.name = name;
        this.saying = 'meow';
    }

    purr() { 
        var i, s = '';
        for (i = 0; i < n; i += 1) {
            if (s) {
                s += '-';
            }
            s += 'r';
        }
        return s;
    }

    get_name() { 
        return this.says() + ' ' + this.name + ' ' + this.says();
    }
}

const myCat = new Cat('Henrietta');
const says = myCat.says(); // 'meow'
const purr = myCat.purr(5); // 'r-r-r-r-r' 
const name = myCat.get_name(); // 'meow Henrietta meow'

advantages

  • ES2015 classes are a simple sugar over the prototype-based inheritance pattern.
  • Provide a much simpler and clearer syntax to create objects and deal with inheritance.
  • Classes support prototype-based inheritance, super calls, instance and static methods and constructors.

Disadvantages

  • ES2015 classes don’t fix any of the aforementioned problems.
  • It's not introducing radically new behavior or a more classical version of inheritance.

JavaScript provides a much richer set
of code reuse patterns

In contrast to most other languages, JS is somewhat unique that you can actually create objects directly without the notion of classes or other abstractions.

JavaScript is a prototypal
inheritance language

Prototypal inheritance

Prototypal inheritance is powerfully expressive, but is not widely understood.

Objects can inherit properties directly
from other objects

Prototypal  pattern

In a purely prototypal pattern, we dispense with classes (or class-like syntax).

We focus instead on the objects.

Prototypal inheritance is conceptually simpler than classical inheritance: a new object can inherit the properties of an old object.

// object literal
var myMammal = {
    name: 'Herb the Mammal',
    get_name : function () {
        return this.name;
    },
    says: function () {
        return this.saying || '';
    }
};

// Object.create method
var myCat = Object.create(myMammal);

// customize the new instances
myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.purr = function (n) {
    var i, s = '';
    for (i = 0; i < n; i += 1) {
        if (s) {
            s += '-';
        }
        s += 'r';
    }
    return s;
};
myCat.get_name = function () {
    return this.says() + ' ' + this.name + ' ' + this.says();
};

var says = myCat.says(); // 'meow'
var purr = myCat.purr(5); // 'r-r-r-r-r' 
var name = myCat.get_name(); // 'meow Henrietta meow'

Improvements

  • Dispense with classes, focusing instead on the objects
  • Conceptually simpler, easy to understand
  • No longer using 'new'

Prototypal inheritance

Instead of calling function constructors like new Bar(), we use Object.create().

Object.create

Added in ES5, a method which allows us to create a new object and optionally provide another object to prototype link it to.

Improvements

  • JavaScript doesn’t need constructor functions because any function can return a new object.
  • With dynamic object extension, object literals and `Object.create()`, we have everything we need — with none of the mess.
  • And `this` behaves just like it does in any other function. Hurray!

Drawbacks

  • Still no privacy

Closures

What is a Closure?

A closure is the combination of a function bundled together with references to it's surrounding state.

Why Closures?

A closure gives you access to an outer function's vars and parameters from an inner function, even after the outer function has returned.

Why Closures?

Among other things, closures are commonly used to give objects data privacy.

Module Pattern

// Module pattern
var mammal = function() {
    var name = 'Herb the Mammal';

    return {
        getName: function() {
            return name;
        },
        says: function() {
            return saying || '';
        }
    };
}());

Privacy

  • The Module pattern encapsulates "privacy" state and organization using closures. 
  • It provides a way of wrapping a mix of public and private methods and variables, protecting pieces from leaking into the global scope and accidentally colliding with another developer's interface.

Privacy

  • With this pattern, only a public API is returned, keeping everything else within the closure private.
  • This gives us a clean solution for shielding logic doing the heavy lifting while only exposing an interface we would like other parts of our application to use.

Disadvantages

  • To change visibility, we have to make changes to each place the member was used.
  • No access to private members in methods that are added to the object at a later point.

Disadvantages

  • Can't easily unit test private members.
  • Can’t easily patch or extend private members.

Revealing Module Pattern

// Revealing module pattern
var mammal = function() {
    var name = 'Herb the Mammal';

    function getName() {
        return name;
    }

    function says() {
        return saying || '';
    }

    return {
        getName: getName,
        says: says
    };
}());

Improvements

  • No need to repeat the name of the main object when to call one public method from another or access public variables (avoiding 'this').
  • No need to switch to object literal notation for the things you wish to make public.

Factory Pattern

In JavaScript, any function can create new objects. When it’s not a constructor function or class, it’s called a factory function

var vehiclePrototype = {
  doors: 4,
  state: 'brand new',
  color: 'silver'
};

var carOptions = {
  color: 'yellow'
};

var truckOptions = {
  wheelSize: 'large',
  state: 'used',
  color: 'blue'
};

// Our Factory method for creating new Vehicle instances
function createVehicle(options) {
  return _.extend(Object.create(vehiclePrototype), options);
}

// Create an instance of our factory that makes cars
var car = createVehicle(carOptions);
console.log(car.color); // 'yellow'
console.log(car.doors); // 4
console.log(car.state); // 'brand new'

// Create an instance of our factory that makes trucks
var truck = createVehicle(truckOptions);
console.log(truck.color); // 'blue'
console.log(truck.wheelSize); // 'large'
console.log(truck.state); // 'used'

Improvements

  • Flexible
  • ​Doesn't require the use of `new`
  • Optionally define private instance variables and methods
  • Standard `this` behavior
  • No deceptive `instanceof`

Drawbacks

  • Doesn’t create a link from the instance to `Factory.prototype`.
  • `this` doesn’t refer to the new object inside the factory.
  • May perform slower than a constructor function in micro-optimization benchmarks.

Functional Pattern

Functional Inheritance

Not to be confused with functional programming

Functional inheritance makes use of a factory function, and then tacks on new properties using concatenative inheritance.

var mammal = function (spec) {
    var that = {};

    that.getName: function() {
        return spec.name;
    };

    that.says: function() {
        return spec.saying || '';
    };

    return that;
};

var myMammal = mammal({name: 'Herb'});

var cat = function (spec) {
    spec.saying = spec.saying || 'meow';

    var that = mammal(spec);

    that.purr = function (n) {
        var i, s = '';
        for (i = 0; i < n; i += 1) {
            if (s) {
                s += '-';
            }
            s += 'r';
        }
        return s;
    };

    that.getName = function() {
        return that.says() + ' ' + spec.name + ' ' + that.says();
    },
    
    return that;
};

var myCat = cat({name: 'Henrietta'});
var says = myCat.says(); // 'meow'
var purr = myCat.purr(5); // 'r-r-r-r-r' 
var name = myCat.getName(); // 'meow Henrietta meow'

Improvements

  • Flexible
  • ​Doesn't require the use of 'new' or 'this'.
  • Optionally defines private instance variables and methods.

Mixin Pattern

Concatenative inheritance

Concatenative inheritance is the process of copying the properties from one object to another, without retaining a reference between the two objects.

  • Tries to mimic the automatic "copying" of inheritance.
  • Manually iterates through all the methods/properties on an object.
  • Makes a "copy" (technically just a reference for functions and objects) onto the target object.
// object literal
var myMammal = {
    name: 'Herb the Mammal',
    get_name : function () {
        return this.name;
    },
    says: function () {
        return this.saying || '';
    }
};

var myCat = _.extend(Object.create(myMammal), {
    name: 'Henrietta',
    saying: 'meow',
    purr: function(n) {
        var i, s = '';
        for (i = 0; i < n; i += 1) {
            if (s) {
                s += '-';
            }
            s += 'r';
        }
        return s;
    },
    get_name = function() {
        return this.says() + ' ' + this.name + ' ' + this.says();
    }
});

var says = myCat.says(); // 'meow'
var purr = myCat.purr(5); // 'r-r-r-r-r' 
var name = myCat.get_name(); // 'meow Henrietta meow'

Advantages and Disadvantages

  • Enables code reuse
  • No prototype link
  • Prototype pollution

Modern Modular Patterns

AMD

A proposal for defining modules in which both the module and dependencies can be asynchronously loaded.

Why AMD

  • Avoids the need to worry about globals, supports named modules, doesn’t require server transformation to function, and useful for dependency management.
  • Significantly cleaner than the present global namespace and <script> tag solutions many of us rely on.
  •  Possible to lazy load scripts if this is needed.

Common JS

  • Module proposal specifies a simple API for declaring modules that work outside of the browser.
  • Takes a server-first approach, assuming synchronous behavior, no global baggage, and attempts to cater for the future (server-side).
  • Only supports objects as modules (Node.js supports constructor functions).

ES2015 Modules

Advantages

  • Synchronous and asynchronous loading supported.
  • Syntactically simple.
  • Support for static analysis tools.

DisAdvantages

  • Not supported everywhere yet.

Unfortunately none of the major JavaScript runtimes support ES2015 modules in their current stable branches. This means no support in Firefox, Chrome or Node.js.

Closing

  1. Don't ​use constructor functions for inheritance.
  2. JavaScript itself is partly to blame because of it's syntax.
  3. Think about delegation over inheritance.
  4. Remember: it's just objects linked to objects!

Recommended Resources:

  • JavaScript The Good Parts by Douglas Crockford
  • JavaScript Patterns by Stoyan Stefanov
  • Learning JavaScript Design Patterns by Addy Osmani
  • Inherited a Mess (article series) by Kyle Simpson

Slides

https://slides.com/jonkemp/methods-of-inheritance

Thank you!

JavaScript: Methods of Inheritance

By Jonathan Kemp

JavaScript: Methods of Inheritance

  • 787