📜 ⬆️ ⬇️

Subtleties of ES6: Inheritance (Part 1)

A couple of weeks ago (the article was written in August - approx. Transl. ) We described a new class system in ES6 in the trivial cases of creating an object constructor. We showed how to write code like this:

class Circle { constructor(radius) { this.radius = radius; Circle.circlesMade++; }; static draw(circle, canvas) { // Canvas drawing code }; static get circlesMade() { return !this._count ? 0 : this._count; }; static set circlesMade(val) { this._count = val; }; area() { return Math.pow(this.radius, 2) * Math.PI; }; get radius() { return this._radius; }; set radius(radius) { if (!Number.isInteger(radius)) throw new Error("Circle radius must be an integer."); this._radius = radius; }; } 

Unfortunately (as some have noticed), we didn’t have time to talk about all the power of the ES6 classes. As in traditional class systems (a la Java and C ++), in ES6 it is possible to inherit, when one class takes the base of another and extends it.

Let's take a closer look at the possibilities of this feature.

Before we go further, it will be useful to spend some time on considering the features of property inheritance and the dynamic prototype chain .
')

Javascript inheritance


When we create an object, we can put properties in it (properties), it also inherits the properties of its prototypes. Javascript programmers are familiar with the existing Object.create API, which makes it easy to do something like this:

 var proto = { value: 4, method() { return 14; } } var obj = Object.create(proto); obj.value; // 4 obj.method(); // 14 


Further, when we add properties to obj with the same name as proto , they overlap ( shadow ) the original ones.

 obj.value = 5; obj.value; // 5 proto.value; // 4 


Basics of Inheritance


With this in mind, we can see how to connect a chain of prototypes of an object created by a class. Remember that when we create a class, we create a new function corresponding to the constructor method in the definition (definition) of the class that contains all the static methods. We also create an object that will be the property of the function we have created and which will contain all the methods of the instance (instance) of the class. To create a new class that inherits all static properties, we must force the new function object to inherit from the function object of the superclass. Similarly, we need to force the prototype object of a new function to inherit from the prototype superclass to ensure that class instance methods inherit.

This is a rather dense explanation. Let's try to show by example how to connect objects without a new syntax, and then we will make a trivial addition to make things aesthetically pleasing.

Continuing the previous example, let's say that we have a class Shape for which we want to create a subclass:

 class Shape { get color() { return this._color; } set color(c) { this._color = parseColorAsRGB(c); this.markChanged(); //  canvas  } } 

When we write code in this way, we face the same problem as before with static properties: we have no syntactic way to change the function prototype during the definition (definition). Although this can be bypassed using Object.setPrototypeOf , this approach is generally less fast and has less optimization options for JS engines than when we have the opportunity to set a function prototype when it is created.

 class Circle { // .  } //    Object.setPrototypeOf(Circle.prototype, Shape.prototype); //    Object.setPrototypeOf(Circle, Shape); 

It's ugly enough. We added syntax classes to encapsulate all the logic in one place, so that you do not need to do any "binding" after. Java, Ruby, and other OO languages ​​have a syntax for declaring one class as a subclass of another, so Javascript must. We use the extends so we can write like:

 class Circle extends Shape { // .  } 

You can put any expression after extends , as long as it has a valid constructor with the prototype property. For example, fit:

You can use null to not inherit from the Object.prototype .

Super properties


So, we can create subclasses, inherit properties, and sometimes our methods overlap ( shadow ) the methods of the parent. What if we want to bypass the overlap mechanism?
Suppose we want to write a Circle subclass that handles (handles) the scaling of a circle by a given number. To achieve this, we can write a rather unnatural class:

 class ScalableCircle extends Circle { get radius() { return this.scalingFactor * super.radius; } set radius() { throw new Error("ScalableCircle radius is constant." + "Set scaling factor instead."); } // ,   scalingFactor } 

Note that the radius getter uses super.radius . This new super keyword allows us to search for a property starting from the prototype, ignoring any shadowing we may have.

Access to super-properties ( super[expr] , by the way, also works) can be used in any function that is declared using the definition syntax (definition) of methods. Although these functions can be pulled out of the original object, the accesses are tied to the object where the method was defined (defined) for the first time. This means that by “pulling out” a method into a local variable (oh, this Javascript - approx. Transl. ), We will not change the behavior of super-access.

 var obj = { toString() { return "MyObject: " + super.toString(); } } obj.toString(); // MyObject: [object Object] var a = obj.toString; a(); // MyObject: [object Object] 


From the translator: for ease of translation (and to see how relevant the topic is for the community) again I divide the article into 2 approximately equal parts.

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


All Articles