📜 ⬆️ ⬇️

Javascript inheritance for dummies

Having read the next smart book about javascript, I began to understand the methods of implementing inheritance in it. Well, that is, of course, everyone understands that in a real, large project, it is best to use a function from some library for this (since there are a lot of them), but you want to understand how it works at all.

I decided to look at what is on this topic in Habré - well, I wish I didn’t do it - someone writes articles about his crutches for a couple of pages because the library function didn’t like something, someone seems to have convenient access to the ancestor by ordinal number. In general, a lot of fun, a lot of code, there is also enough debate, but the base itself is simply not there. And it seemed to me that it would be nice to fix it.


1. So, what is the actual problem?


The problem is that in javascript there is no usual inheritance. The maximum that can be done is to create a class constructor:
')
function Class(){/*   *} 

And then, through the prototype, add methods, constants and static variables, which will be one for all instances.

 Class.prototype = {/**/} 

This is in most cases enough, but sometimes you want more.

2. And if you really want ...


... then you can implement it. The classic way is:

 function inherit_A(Child, Parent) { var F = function () { }; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.super = Parent.prototype; } 

And you can even use it:

 $(document).ready(function () { function Man(name) { this.name = name } Man.prototype.say = function () { console.log("My name is " + this.name) } function Gentleman(name) { Man.call(this, name); } inherit_A(Gentleman, Man); }); 

I have with him, however, there is one small problem - usually, to create a prototype, I use object notation, as shown above:

 Class.prototype = { /**/ } 

And, with this inheritance, you don’t really use this method - either my functions will completely overwrite the inherited prototype, or the inherited prototype will completely overwrite my functions. Fortunately, you can inherit differently.

3. The method is dedicated to all who love object literals.


Here he is:

 function inherit_B(Child, Parent) { var F = function () { }; F.prototype = Parent.prototype; var f = new F(); for (var prop in Child.prototype) f[prop] = Child.prototype[prop]; Child.prototype = f; Child.prototype.super = Parent.prototype; } 

It simply takes, and copies all the properties from the prototype of the parent to the prototype of the heir, so now my favorite object literal works as it should:

 $(document).ready(function () { function Man(name) { this.name = name } Man.prototype = { constructor: Man, THOUGHTS: "wanna beer!", say: function () { console.log("My name is " + this.name + " and i think:'" + this.THOUGHTS + "'") } } function Gentleman(name, prefered_beverage) { Man.call(this, name); this.prefered_beverage = prefered_beverage; } Gentleman.prototype = { constructor: Gentleman, THOUGHTS: "it's teatime!" } inherit_B(Gentleman, Man) function Programmer(name, prefered_lang) { Gentleman.call(this, name, "Cofee"); this.prefered_lang = prefered_lang; } Programmer.prototype = { constructor: Programmer, THOUGHTS: "runtime error 138? wanna debug XD!" } inherit_B(Programmer, Gentleman) var man = new Man("Jack"); var gentleman = new Gentleman("John", "Orange pekoe"); var programmer = new Programmer("James", "C++"); man.say(); gentleman.say(); programmer.say(); }); 

And the console agrees with me:

 sample.js:11 My name is Jack and i think:'wanna beer!' sample.js:11 My name is John and i think:'it's teatime!' sample.js:11 My name is James and i think:'runtime error 138? wanna debug!' 

Fine. And now let's imagine a hypothetical situation where there are a lot of parent classes, and you need, for example, to call some method that was overloaded 2-3 classes back. In such a situation, of course, it is better to take a closer look at the architecture of your application. Now suppose that everything there works as intended, then it’s damn inconvenient to write, for example, like this:

 this.super.super.super.someMethod.apply(this) 

4. But this is also solved.


 function inherit_C(Child, Parent) { var F = function () { }; F.prototype = Parent.prototype; var f = new F(); for (var prop in Child.prototype) f[prop] = Child.prototype[prop]; Child.prototype = f; Child.prototype[Parent.prototype.__class_name] = Parent.prototype; } 

This function adds an object to the child's prototype with reference to the parent's prototype. To make it work as it should, add a field to the prototype of each class.

 __class_name 

For example, suppose that the class BadProgrammer has appeared in our hierarchy, which just needed convenient access to the prototype of a distant ancestor. We slightly modify the previous example:

 $(document).ready(function () { function Man(name) { this.name = name } Man.prototype = { __class_name: "Man", constructor: Man, THOUGHTS: "wanna beer!", say: function () { console.log("My name is " + this.name + " and i think:'" + this.THOUGHTS + "'") } } function Gentleman(name, prefered_beverage) { Man.call(this, name); this.prefered_beverage = prefered_beverage; } Gentleman.prototype = { __class_name: "Gentleman", constructor: Gentleman, THOUGHTS: "it's teatime!" } inherit_C(Gentleman, Man) function Programmer(name, prefered_lang) { Gentleman.call(this, name, "Cofee"); this.prefered_lang = prefered_lang; } Programmer.prototype = { __class_name: "Programmer", constructor: Programmer, THOUGHTS: "runtime error 138? wanna debug XD!" } inherit_C(Programmer, Gentleman) function BadProgrammer(name) { Programmer.call(this, name, "brainfuck"); } BadProgrammer.prototype = { __class_name: "BadProgrammer", constructor: BadProgrammer, THOUGHTS: "runtime error 138? wanna debug XD!", say: function () { this.THOUGHTS = this.Man.THOUGHTS; this.Man.say.apply(this); } } inherit_C(BadProgrammer, Programmer) var man = new Man("Jack"); var gentleman = new Gentleman("John", "Orange pekoe"); var programmer = new Programmer("James", "C++"); var badprogrammer = new BadProgrammer("Jake"); man.say(); gentleman.say(); programmer.say(); badprogrammer.say(); }); 

The BadProgrammer class used the thoughts of the very first link in our evolutionary class chain, and now it’s not thinking at all about what programmers usually think;)

 My name is Jack and i think:'wanna beer!' My name is John and i think:'it's teatime!' My name is James and i think:'runtime error 138? wanna debug!' My name is Jake and i think:'wanna beer!' 

5. And one more thing


I can not imagine in some cases it may be useful, but maybe someday, someone may need multiple inheritance. It is also quite possible to implement:

 function inhertit_multiple(child) { for( var i = 1; i < arguments.length; ++i ) { var parent = arguments[i] for (var prop in parent.prototype) { if (!child.prototype[prop]) child.prototype[prop] = parent.prototype[prop]; } child.prototype[parent.prototype.__class_name] = parent.prototype; } } 

Not very much different from the previous version.
In order to show how it works, I came up with another quite realistic example:

 $(document).ready(function () { function Mammy() { this.mammy_message = "You Dont love me!" } Mammy.prototype = { __class_name: "Mammy", say_something_wise: function () { console.log(this.mammy_message) } } function Daddy() { this.daddy_message = "I just don't want to be a dad!" } Daddy.prototype = { __class_name: "Daddy", say_something_wise: function () { console.log(this.daddy_message) } } function Lad() { this.lad_message = "And i want a candy!"; Mammy.apply(this); Daddy.apply(this); } Lad.prototype = { __class_name: "Lad", say_something_wise: function () { this.Daddy.say_something_wise.call(this); this.Mammy.say_something_wise.call(this); console.log(this.lad_message); } } inhertit_multiple(Lad, Mammy, Daddy) var lad = new Lad(); lad.say_something_wise(); }); 

If this is run, then something like what we expect will appear in the console.

 I just don't want to be a dad! You Dont love me! And i want candy! 


That's how it all works. All the code and examples put here - suddenly come in handy.

UPD. As one good person rightly noted, in all methods except the first, instanceof refuses to recognize the class as an instance of the parent class. With multiple inheritance, this deficiency persisted, but for other cases I eliminated it.

Source: https://habr.com/ru/post/131714/


All Articles