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
- Expressing classes and inheritance in pure JavaScript is difficult.
- Trying to mimic "classical" inheritance in JavaScript, we miss a powerful feature that was built into the language from the start.
- 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
- Try to retrieve a property value from an object, and it doesn't exist.
- 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
- Create a function which we use as a "constructor"
- Call that function with `new` so that we can "instantiate" our "class"
- 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
- Creates an empty object
- Sets the prototype of this object to the prototype property of the constructor
- Calls the constructor function with `this` pointing to the newly-created object
- 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
- Don't use constructor functions for inheritance.
- JavaScript itself is partly to blame because of it's syntax.
- Think about delegation over inheritance.
- 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