Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Classical Inheritance in Javascript

Classical Inheritance in Javascript

In this talk we dive deep into Javascript's mechanics of inheritance

Thanos Polychronakis

February 28, 2018
Tweet

More Decks by Thanos Polychronakis

Other Decks in Technology

Transcript

  1. Who is Thanasis Professional Community Open Source Eng Mgr at

    Waldo CTO at QallOut CTO at Insight Replay Founder Over 40 NPM packages Contributor to major Node.js packages Avid OSS author Local Node.js Meetup organizer (Greece) founder organizer skgtech.io DEVit Conference Software Engineer, CTO, founder Recently moved to London from Greece Available for hire
  2. 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
  3. Why Classical / Prototypical? class Animal {} var cat =

    new Animal(); cat instanceof Animal; // true ES6 "class" keyword is prototypical inheritance
  4. Why Classical / Prototypical? var util = requite('util'); // Parent

    Ctor var Animal = function() {}; var Cat = function() { Animal.call(this); }; util.inherits(Cat, Animal); For ES5 Node.js Provides helpers built in the language (versions 6 and bellow)
  5. Why Classical / Prototypical? Alternatives to Classical Inheritance A bunch

    of variations on how to do Classical (Object.create, Object.assign) Composition Object literals and the rest...
  6. Why Classical / Prototypical? Closing points onto WHY It is

    the language's intended way for inheritance. Generally accepted as the way to do inheritance in Javascript. Signal / Noise ratio in JS land is a problem, stay confident & focused.
  7. How to Inherit // Parent Ctor class AnimalES6 { constructor(name)

    { this.name = name; } } class Cat extends AnimalES6 { constructor(color) { super('cat'); //call the parent method with super this.color = color; } } ES6 and beyond
  8. How to Inherit var util = require('util'); // Parent Ctor

    var Animal = function() {}; var Cat = function() { Animal.call(this); }; util.inherits(Cat, Animal); ES5 in Node.js
  9. 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); How "util.inherits()" works
  10. 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
  11. 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();
  12. 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
  13. 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
  14. 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!
  15. The Javascript Prototype All variables of any type in Javascript

    have the __proto__ property! 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
  16. Inheritance Explained var inherits = function(ChildCtor, ParentCtor) { function TempCtor()

    {}; TempCtor.prototype = ParentCtor.prototype; ChildCtor.prototype = new TempCtor(); ChildCtor.prototype.constructor = ChildCtor; };
  17. Inheritance Explained 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.
  18. Inheritance Explained 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
  19. Inheritance Explained 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.
  20. Inheritance Explained 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.
  21. Inheritance Explained 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
  22. Inheritance Explained 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
  23. Inheritance Explained var inherits = function(ChildCtor, ParentCtor) { ClildCtor.prototype =

    ParentCtor.prototype; }; This isn't a "copy", this is "assignment by reference" Every change you perform on the Child's prototype will directly change the Parent's prototype, they are the same Why not:
  24. Inheritance Explained var inherits = function(ChildCtor, ParentCtor) { ClildCtor.prototype =

    new ParentCtor() }; This is a "copy" BUT you are also invoking the Parent's Ctor which can have severe side-effects Why not:
  25. The Inheritance 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!
  26. 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!
  27. 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
  28. The Scope Base Definition var Animal = function(name) { this.name

    = name; }; Animal.prototype.getName = function() { return this.name; };
  29. 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"
  30. 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"
  31. 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 onEvent(animal.onEventHandler); // this is ok onEvent(animal.onEventHandler.bind(animal)); // With Arrow function onEvent(() => animal.onEventHandler);
  32. 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 define your methods. Can even overwrite the parent's 1 3 2
  33. Inheritance Ctor // Inherits from Animal Class Cat extends Animal

    { constructor (name, color) { super(name); this.color = color; } getColor() { return this.color; } } 1. First thing you do in your Ctor is to call your Parent's Ctor with super 2. Afterwards define your methods. Can even overwrite the parent's 1 2 From Node 8
  34. 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
  35. 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 = '';
  36. 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);
  37. Common Gotchas Scope on callbacks Animal.prototype.getName = function(cb) { this.db.getName(function(name)

    { // NEW SCOPE HERE, "this" REFERS TO // THIS FUNCTION'S CONTEXT cb(name); }); }; Use .bind(this) at end of the anonymous function expression used as a callback Use the Arrow Function Expression ( fat arrow => )
  38. Trivia & 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() {}
  39. Trivia & Best Practises Use the constructor strictly to construct

    the instance Asynchronous operations in Ctors 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; }
  40. Trivia & 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 };