Inheritance in JS
class: center, middle .title[ Front-end training # Inheritance ] --- # Inheritance Is a way to reuse code of existing objects, or to establish a subtype from an existing object, or both, depending upon programming language support. --- # Reusability Let's consider next situation: you have 2 object to represent student and teacher. ``` var student = { name: 'Vasya', describe: function() { console.log(this.name); } }; var teacher = { name: 'Vasili Alibabayevich', describe: function() { console.log(this.name); } }; // ... ``` Note `describe` repeated... --- # Reusability Naive reuse of `describe` might look like this: ``` function describe(person) { console.log(person.name); } describe(student); // -> "Vasya" describe(teacher); // -> "Vasili Alibabayevich" ``` But such approach will create many functions without explaining their purpose; May lead to name conflicts and lost object context: ``` var cargo = { weight: 200, height: 100, width: 200 }; describe(cargo); // -> undefined // whoops... ``` --- # Extending objects with merge This is where merge helps: ``` function merge(a, b){ /*copies properties from b to a*/ } var person = { describe: function() { console.log(this.name); } }; var student = merge(person, { name: 'Vasya' }); var teacher = merge(person, { name: 'Vasili Alibabayevich' }); student.describe(); // -> "Vasya" teacher.describe(); // -> "Vasili Alibabayevich" ``` This approach also called "mixin", since you basically mix parent object methods and properties to your object. merge implementations: [jQuery.extend](http://api.jquery.com/jQuery.extend/), [underscore.extend](http://underscorejs.org/#extend), [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) --- # Prototype Unlike "traditional" class-based approach, JS uses constructors and `[[prototype]]` objects as default extension mechanism. You need to assign prototype object to constructor property called `prototype`, and when you create a new object - this object will be automatically assigned as a prototype: ```javascript var someObject = { name: 'test' } // create an object linked to `someObject` var newObject = Object.create(someObject) newObject.name; // 'test' ``` So, we have someObject that is now `[[Prototype]]` linked to newObject. Clearly newObject.name doesn't actually exist, but nevertheless, the property access succeeds (being found on anotherObject instead) and indeed finds the value test. --- class: center, middle  --- class: center, middle # But where exactly does the [[Prototype]] chain "end" ? --- The top-end of every *normal* `[[Prototype]]` chain is the built-in `Object.prototype`. This object includes a variety of common utilities used all over JS, because all normal objects in JavaScript "descend from" the `Object.prototype` object. Some of them: * `Object.prototype.toString()` - Returns a string representation of the object. * `Object.prototype.hasOwnProperty()` - Returns a boolean indicating whether an object contains the specified property as a direct property of that object and not inherited through the prototype chain. * `Object.prototype.isPrototypeOf()` - Returns a boolean indication whether the specified object is in the prototype chain of the object this method is called upon. --- class: center, middle  --- # Inheritance example ```javascript var Person = function(name) { this.name = name; }; Person.prototype.greet = function() { console.log('Hi, I am ' + this.name); }; var Employee = function(name, title) { Person.call(this, name); this.title = title; }; Employee.prototype = Object.create(Person.prototype); Employee.prototype.constructor = Employee; Employee.prototype.greet = function() { console.log('Hi, I am ' + this.name + ', the ' + this.title); }; var Customer = function(name, money) { Person.call(this, name); this.money = money; }; Customer.prototype = Object.create(Person.prototype); Customer.prototype.constructor = Customer; var bob = new Employee('Bob', 'Builder'); bob.greet(); // Hi, I am Bob, the Builder var joe = new Customer('Joe', 2000); joe.greet(); // Hi, I am Joe var rg = new Employee('Red Green', 'Handyman'); rg.greet(); // Hi, I am Red Green, the Handyman ``` --- # ES2015 version ```javascript class Person { constructor(name) { this.name = name; } greet() { console.log('Hi, I am ' + this.name); } } class Employee extends Person { constructor(name, title) { super(name); this.title = title; } greet() { console.log('Hi, I am ' + this.name + ', the ' + this.title); } } class Customer extends Person { constructor(name, money) { super(name); this.money = money; } } var bob = new Employee('Bob', 'Builder'); bob.greet(); // Hi, I am Bob, the Builder var joe = new Customer('Joe', 2000); joe.greet(); // Hi, I am Joe var rg = new Employee('Red Green', 'Handyman'); rg.greet(); // Hi, I am Red Green, the Handyman ``` --- # Change prototype properties When you change prototype object - all childs are automatically updated: ``` function Dog(name) { this.name = name; } var dog = new Dog('Fido'); var doge = new Dog('Shiba'); dog.sayName(); // -> dog.sayName is not a function Dog.prototype.sayName = function() { console.log(this.name); }; dog.sayName(); // Fido doge.sayName(); // Shiba ``` --- # Change prototype properties (cont.) But notice, that when you change properties of specific prototype - only that constructor childs updated: ``` function Animal() {} function Cat(name) { this.name = name; } Cat.prototype = Object.create(Animal.prototype); Cat.prototype.constructor = Animal; function Dog(name) { this.name = name; } Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Animal; var cat = new Cat('Kitty'); var dog = new Dog('Fido'); var doge = new Dog('Shiba'); Dog.prototype.woof = function() { console.log(this.name + ' say "woof"'); }; // Fido say "woof" // Shiba say "woof" dog.woof(); dog.woof(); cat.woof(); // TypeError: undefined is not a function ``` --- # Detect property source You can find out where property or method comes from - object itself or prototype; by using `hasOwnProperty(key)`. ``` cat.hasOwnProperty('name'); // true cat.hasOwnProperty('sayName'); // false ``` Prototype methods and properties are captured by `for..in` loop, so if you don't need them - use `hasOwnProperty`. ``` for (var key in cat) { console.log(key); } // name, sayName ``` ``` for (var key in cat) { if (cat.hasOwnProperty(key)) console.log(key); } // name ``` --- # Sensitive data in prototypes Never store data that may change in prototype ``` function Animal() { this.fed = false; } // "sensitive" data Animal.prototype.items = []; function Cat(name) { this.name = name; } Cat.prototype = Object.create(Animal.prototype); Cat.prototype.constructor = Animal; Cat.prototype.give = function(item) { this.items.push(item); }; var kitty = new Cat('kitty'); var cat = new Cat('cat'); cat.give('food'); cat.items; // ['food'] kitty.items; // ['food'] -> bad, we didn't give anything to kitty ``` --- # Method override If you need to change an object behaviour - you can rewrite prototype's object method: ``` Cat.prototype.sayName = function() { return 'cat meows "' + this.name + '"'; }; var cat = new Cat('Kitty'); var dog = new Dog('Fido'); dog.sayName(); // Fido cat.sayName(); // cat meows "Kitty" ``` Also you can change properties only for object: ``` var dog = new Dog('Fido'); var doge = new Dog('Shiba'); doge.woof = function() { console.log("wow, so woof, many sound"); }; dog.woof(); // Fido say "woof" doge.woof(); // wow, so woof, many sound ``` --- # Standard objects prototypes Also you may add some methods to existing types: ``` String.prototype.isString = true; Array.prototype.isArray = true; Number.prototype.isNumber = true; (1).isNumber // true [].isArray // true ''.isString // true ``` All js objects has Object as prototype. You may define some behaviour for _all_ object using `Object.prototype`. For example add method to get unique id of object: ``` Object.prototype.getId = function() { if (!this.___uniq_id) { this.___uniq_id = Math.round(Math.random() * 100500)); } return this.___uniq_id; }; var a = 1; a.getId(); // -> some random id ``` --- # Composition Composition is a way to combine simple object to more complex objects. _Think about automobile: it is made of wheels, engine, driver's place, etc._ ``` function Human() { this.brain = new Brain(); this.legs = new Legs(); } Human.prototype.think = function(idea) { return this.brain.acceptsIdea(idea); }; Human.prototype.walk = function(distance) { this.legs.moveFor(distance); }; var vasya = new Human(); if (vasya.think('beer') == true) { vasya.walk(getDistanceToShop()); } ``` Composition also is a way to reuse existing functionality, w/o redefining it: `Human` use functionality of `Legs` and `Brain`, but is not a direct child of these objects. --- # Resources - http://eloquentjavascript.net/06_object.html - http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/ - [Common misconceptions about inheritance in js](https://medium.com/javascript-scene/common-misconceptions-about-inheritance-in-javascript-d5d9bab29b0a) - [Composition over Inheritance (video, 8min)](https://www.youtube.com/watch?v=wfMtDGfHWpA)