Share
Embed
Classical Inheritance in Javascript
in this presentation we'll dive into Classical Prototypical inheritance in Javascript and node.js
Thanos Polychronakis
September 15, 2015
Transcript
58 slides
-
Inheritance in Javascript Inheritance in Javascript Lots of ways It's
complicated We'll talk about Classical / Prototypical -
Why Classical / Prototypical? var Animal = function() {}; var
cat = new Animal(); cat instanceof Animal; // true Forces use of a Constructor Uses the "new" keyword "instanceof" works Utilizes all the language features provided for inheritance -
Why Classical / Prototypical? var util = requite('util'); // Parent
Ctor var Animal = function() {}; var Cat = function() { Animal.call(this); }; util.inherits(Cat, Animal); Node.js Provides helpers built in the language -
Why Classical / Prototypical? It really has the most widespread
usage in the wild. -
How to Inherit How to Inherit var util = require('util');
// Parent Ctor var Animal = function() {}; var Cat = function() { Animal.call(this); }; util.inherits(Cat, Animal); Node.js -
How to Inherit How to Inherit var inherits = function(ChildCtor,
ParentCtor) { function TempCtor() {}; TempCtor.prototype = ParentCtor.prototype; ChildCtor.prototype = new TempCtor(); ChildCtor.prototype.constructor = ChildCtor; }; // Parent Ctor var Animal = function() {}; var Cat = function() { Animal.call(this); }; inherits(Cat, Animal); Vanilla Javascript ES3 -
How to Inherit How to Inherit var inherits = function(ChildCtor,
ParentCtor) { function TempCtor() {}; TempCtor.prototype = ParentCtor.prototype; ChildCtor.prototype = new TempCtor(); ChildCtor.prototype.constructor = ChildCtor; }; // Parent Ctor var Animal = function() {}; var Cat = function() { Animal.call(this); }; inherits(Cat, Animal); Vanilla Javascript ES3 WTF¿??!¡¿ -
How to Inherit How to Inherit var inherits = function(ChildCtor,
ParentCtor) { ChildCtor.prototype = Object.create(ParentCtor.prototype); ChildCtor.prototype.constructor = ParentCtor; }; // Parent Ctor var Animal = function() {}; var Cat = function() { Animal.call(this); }; inherits(Cat, Animal); Vanilla Javascript ES5 -
How to Inherit How to Inherit var inherits = function(ChildCtor,
ParentCtor) { ChildCtor.prototype = Object.create(ParentCtor.prototype); ChildCtor.prototype.constructor = ParentCtor; }; // Parent Ctor var Animal = function() {}; var Cat = function() { Animal.call(this); }; inherits(Cat, Animal); Vanilla Javascript ES5 meh... -
How to Inherit How to Inherit // Parent Ctor class
AnimalES6 { constructor(name) { this.name = name; } } class Cat extends AnimalES6 { constructor(color) { this.color = color; super('cat'); //call the parent method with super } } Vanilla Javascript ES6 -
How to Inherit How to Inherit // Parent Ctor class
AnimalES6 { constructor(name) { this.name = name; } } class Cat extends AnimalES6 { constructor(color) { this.color = color; super('cat'); //call the parent method with super } } Vanilla Javascript ES6 COOL! ... but it's sugar -
What did just What did just happen? happen? ... lets
talk about the ... lets talk about the prototype prototype -
The Javascript Prototype The Javascript Prototype Javascript is a prototypical
language All Objects have a prototype Everything in JS is an Object The Prototype is the blueprint for creating objects and instances The Instances or Objects do not have a prototype Functions have a prototype var str = new String(); var bool = new Boolean(); var num = new Number(); -
The Javascript Prototype The Javascript Prototype The Prototype is the
blueprint for creating objects and instances var Animal = function(name) { this.name = name; }; Animal.prototype.getName = function() { return this.name; }; var pony = new Animal('pony'); pony.getName(); // "pony" The instance Invokes the Constructor The Constructor Instance local variable A method -
The Javascript Prototype The Javascript Prototype Going down the rabbit
hole... var fn = function() {} fn.prototype // fn {} // -> constructor: function() // -> __proto__: Object All prototypes have at least two properties: constructor: A Function that gets invoked when constructing the instance __proto__: A reference to the parent prototype -
The Javascript Prototype The Javascript Prototype Going downer the rabbit
hole... var str = 'a string'; str.__proto__; // Outputs the Object that was used to construct // the "str" instance: // String {length: 0, [[PrimitiveValue]]: ""} All variables of any type in Javascript have the __proto__ property! -
The Javascript Prototype The Javascript Prototype All variables of any
type in Javascript have the __proto__ property! Remember! Remember! __proto__ is a reference Points to the Object that constructed the instance Instances using the "new" keyword have a __proto__ that points to the Ctor -
So WTF inheritance? So WTF inheritance? var inherits = function(ChildCtor,
ParentCtor) { function TempCtor() {}; TempCtor.prototype = ParentCtor.prototype; ChildCtor.prototype = new TempCtor(); ChildCtor.prototype.constructor = ChildCtor; }; -
So WTF inheritance? So WTF inheritance? var inherits = function(ChildCtor,
ParentCtor) { function TempCtor() {}; TempCtor.prototype = ParentCtor.prototype; ChildCtor.prototype = new TempCtor(); }; ChildCtor.prototype.constructor = ChildCtor; Create a temporary Constructor to copy the Parent Prototype on. -
So WTF inheritance? So WTF inheritance? var inherits = function(ChildCtor,
ParentCtor) { function TempCtor() {}; TempCtor.prototype = ParentCtor.prototype; ChildCtor.prototype = new TempCtor(); }; ChildCtor.prototype.constructor = ChildCtor; Copy the Parent's Prototype to the temporary Constructor's prototype -
So WTF inheritance? So WTF inheritance? var inherits = function(ChildCtor,
ParentCtor) { function TempCtor() {}; TempCtor.prototype = ParentCtor.prototype; ChildCtor.prototype = new TempCtor(); }; ChildCtor.prototype.constructor = ChildCtor; Instanciate the temporary Ctor and assign it by overwriting the Child's prototype. This is where inheritance happens. -
So WTF inheritance? So WTF inheritance? var inherits = function(ChildCtor,
ParentCtor) { function TempCtor() {}; TempCtor.prototype = ParentCtor.prototype; ChildCtor.prototype = new TempCtor(); }; ChildCtor.prototype.constructor = ChildCtor; Let's break this out, too much happened here: "var inst = new TempCtor()" we created an instance The instance has a __proto__ referencing the TempCtor Which in our case is the Parent's Prototype. -
So WTF inheritance? So WTF inheritance? var inherits = function(ChildCtor,
ParentCtor) { function TempCtor() {}; TempCtor.prototype = ParentCtor.prototype; ChildCtor.prototype = new TempCtor(); }; ChildCtor.prototype.constructor = ChildCtor; We overwrite the prototype's special property "constructor" Using the actual Child Ctor Remember, the Ctor is a function -
So WTF inheritance? So WTF inheritance? var inherits = function(ChildCtor,
ParentCtor) { function TempCtor() {}; TempCtor.prototype = ParentCtor.prototype; ChildCtor.prototype = new TempCtor(); }; ChildCtor.prototype.constructor = ChildCtor; So essentially we discard the default Child's prototype Overwriting it with the instance of the Parent Ctor -
The Inheritance The Inheritance var util = require('util'); var Animal
= function(name) { this.name = name; }; Animal.prototype.getName = function() { return this.name; }; // Inherits from Animal var Cat = function(name, color) { Animal.call(this, name); this.color = color; }; util.inherits(Cat, Animal); Cat.prototype.getColor = function() { return this.color; }; That's the key Part right there! -
The Inheritance The Inheritance So... So... We copied the Parent's
prototype to the Child... ... now we need to make sure that all the Constructors from our Parents will be invoked! ... in the right Scope! -
The Scope The Scope 99% of your problems will be
cause of Scope SO PAY SO PAY ATTENTION ATTENTION -
The Scope The Scope Scope refers to where variables and
functions are accessible, and in what context it is being executed. In the case of an Instance, scope is critical in maintaining access to methods and local properties -
The Scope The Scope Base Definition var Animal = function(name)
{ this.name = name; }; Animal.prototype.getName = function() { return this.name; }; -
The Scope The Scope Retains Scope - Vanilla var animal
= new Animal('cat'); animal.getName(); // "cat" Looses Scope var animal = new Animal('cat'); var getName = animal.getName; getName(); // undefined Retains Scope - bind var animal = new Animal('cat'); var getName = animal.getName.bind(animal); getName(); // "cat" -
The Scope The Scope Retains Scope - call var animal
= new Animal('cat'); var getName = animal.getName; getName.call(animal); // "cat" Retains Scope - apply var animal = new Animal('cat'); var getName = animal.getName; getName.apply(animal); // "cat" -
The Scope The Scope In Practice... var Animal = function(name)
{ this.name = name; // this will fail this.on('some event', this.doSomething); // this is ok this.on('some event', this.doSomething.bind(this)); }; var animal = new Animal('cat'); // this will fail $('body').on('load', animal.onLoad); // this is ok $('body').on('load', animal.onLoad.bind(animal)); -
Inheritance Ctor Inheritance Ctor // Inherits from Animal var Cat
= function(name, color) { Animal.call(this, name); this.color = color; }; util.inherits(Cat, Animal); Cat.prototype.getColor = function() { return this.color; }; 1. First thing you do in your Ctor is to call your Parent's Ctor using your own, new, context (scope) 2. Then, right after the Ctor, invoke the inherits function 3. Afterwards we define our own methods and can even overwrite the parent's 1 3 2 -
The Prototype Chain The Prototype Chain The more you extend
a Ctor creating childs the longer the chain gets Js will lookup serially for a method all the way up to the last prototype This can result in performance hits in some particular cases -
Common Gotchas Common Gotchas Only define methods on the prototype
Everything else on the Ctor var Animal = function(name) { this.name = name; }; // Never do this Animal.prototype.name = ''; Animal.prototype.getName = function() { return this.name; }; var cat = new Animal('cat'); var dog = new Animal('dog'); cat.getName(); // "dog" -
Common Gotchas Common Gotchas Never invoke the inheritance method after
your methods var util = require('util'); var Animal = function(name) { this.name = name; }; // Inherits from Animal var Cat = function(name, color) { Animal.call(this, name); this.color = color; }; Cat.prototype.getColor = function() { return this.color; }; // Will overwrite and DELETE getColor() util.inherits(Cat, Animal); -
Common Gotchas Common Gotchas Scope on methods Animal.prototype.getName = function(cb)
{ this.db.getName(function(name) { // NEW SCOPE HERE, "this" REFERS TO // THIS FUNCTION'S CONTEXT cb(name); }); }; -
Common Gotchas Common Gotchas Scope on methods Animal.prototype.getName = function(cb)
{ if (this.name) { cb(this.name); } else { var self = this; this.db.getName(function(name) { self.name = name; cb(name); }); } }; -
Rules & Best Practises Rules & Best Practises We call
Constructors "Constructors" and not "Classes" because they do not behave like Classes They resemble a Class, thus the term "Classical Inheritance" But in reality, this is "Prototypical Inheritance" We signify a Ctor by capitalizing the first letter var Animal = function() {} -
Rules & Best Practises Rules & Best Practises Use the
constructor strictly to construct the instance Asynchronous operations are forbidden! Define any and all properties in the constructor Use null to initialize properties with no value Never return any value from the Ctor It will screw up everything var Animal = function() { this.name = null; this.color = null; } -
Rules & Best Practises Rules & Best Practises You may
define a method on the Ctor directly, that is considered a Static function and will not be inherited var Animal = function() { this.name = null; this.color = null; } Animal.staticFn = function() { // Will not be inherited }; -
Simple Interface Simple Interface CIP CIP var cip = require('cip');
var Child = cip.extend(); var GrandChild = Child.extend(); -
The Constructor The Constructor CIP CIP var cip = require('cip');
var Animal = cip.extend(function(name) { this.name = name; }); var Cat = Animal.extend(function(color) { this.color = color; this.name = 'cat'; }); var gilly = new Cat('black'); gilly instanceof Animal; // true -
Singleton Pattern Singleton Pattern CIP CIP var cip = require('cip');
var Animal = cip.extend(function(name) { this.name = name; }); var Cat = Animal.extendSingleton(function() { this.name = 'cat'; }); var theCat = Cat.getInstance(); var anotherCat = new Cat(); theCat === anotherCat; // true -
Mixins Mixins CIP CIP var cip = require('cip'); var Behavior
= cip.extend(function() { this.mood = 'awesome'; }); var Animal = cip.extend(function(name) { this.name = name; }); cip.mixin(Animal, Behavior); var cat = new Animal('cat'); cat.mood; // 'awesome' -
Casting Casting CIP CIP // Use EventEmitter as the base
Constructor. var EventEmitter = require('events').EventEmitter; var cip = require('cip'); var CeventEmitter = cip.cast(EventEmitter); var Thing = CeventEmitter.extend(); var newThing = new Thing(); newThing instanceof CeventEmitter; // true newThing instanceof EventEmitter; // true newThing.on('nextThing', doSomething);