This is the second article of relearning JS series, the original intention of writing this series is also to consolidate their JS foundation. Since it is to learn again, certainly won’t introduce a knowledge point from zero, if have the content that encounter won’t please search data by oneself.

The prototype

Inheritance depends on the prototype, and of course the prototype is not the focus of this article, so let’s review it.

The concept of a prototype is simple:

  • All objects have a property__proto__Point to an object, which is a prototype
  • The prototype of each object can be passedconstructorFind the constructor, the constructor can also passprototypeFind the prototype
  • All functions can pass__proto__findFunctionobject
  • All objects can pass through__proto__findObjectobject
  • Pass between objects__proto__It’s called the prototype chain. Properties that do not currently exist on an object can be searched up the prototype chain to the top levelObjectobject

In fact, the most important content of the prototype is these, there is no need to read those long articles on what is a prototype, beginners will read more and more confused.

Of course, if you want to learn more about prototypes in depth, you can read my previous post.

ES5 implements inheritance

ES5 implementation inheritance in general, there are two ways, before writing about this aspect of the content, directly copy to use.

In general, I think this part is more for the interview at present.

Combination of inheritance

Combinatorial inheritance is the most common inheritance method.

function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}
function Child(value) {
  Parent.call(this, value)
}
Child.prototype = new Parent()

const child = new Child(1)

child.getValue() / / 1
child instanceof Parent // true
Copy the code

Call (this) inherits the attributes of the Parent class in the constructor of the child class, and then changes the prototype of the child class to new Parent() to inherit the functions of the Parent class.

The advantage of this inheritance method is that the constructor can pass parameters and will not be shared with the reference attributes of the parent class, and the function of the parent class can be reused. However, there is also a disadvantage that the parent class constructor is called when the parent class function is inherited, resulting in more unnecessary parent class attributes on the prototype of the subclass, resulting in a waste of memory.

Parasitic combinatorial inheritance

This method of inheritance optimizes composite inheritance. The disadvantage of composite inheritance is that the constructor is called when inheriting from the superclass function. We just need to optimize this.

function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}

function Child(value) {
  Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    value: Child,
    enumerable: false.writable: true.configurable: true}})const child = new Child(1)

child.getValue() / / 1
child instanceof Parent // true
Copy the code

The core of the inheritance implementation above is to assign the prototype of the parent class to the child class, and set the constructor to the child class, which not only solves the problem of useless parent class attributes, but also can correctly find the constructor of the child class.

How does Babel compile ES6 classes

The reason why WE said earlier that ES5 inheritance is more for interviews is that we can now implement inheritance directly using class.

But class is an ES6 thing, and in order to make it browser-compatible, we usually compile ES6 code through Babel. Let’s take a look at what code compiled with Babel looks like.


function _possibleConstructorReturn (self, call) { 
    // ...
    return call && (typeof call === 'object' || typeof call === 'function')? call : self; }function _inherits (subClass, superClass) { 
    // ...
    subClass.prototype = Object.create(superClass && superClass.prototype, { 
        constructor: { 
            value: subClass, 
            enumerable: false.writable: true.configurable: true}});if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}


var Parent = function Parent () {
    // Verify if Parent constructs this
    _classCallCheck(this, Parent);
};

var Child = (function (_Parent) {
    _inherits(Child, _Parent);

    function Child () {
        _classCallCheck(this, Child);
    
        return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this.arguments));
    }

    return Child;
}(Parent));
Copy the code

The code above is compiled to hide some non-core code, so we’ll start with the _inherits function.

The code for setting the subclass prototype is exactly the same as that for parasitic composite inheritance, which is the best way to do it. Object. SetPrototypeOf (subClass, superClass), which is used to inherit static methods from the parent class.

Then the Child constructor piece of code is basically the same as the previous implementation. So in general, Babel implements inheritance in the same way as parasitic combinatorial inheritance, implementing more than one static method that inherits its parent class.

Problems with inheritance

With all this talk about how to implement inheritance, now let’s think about whether inheritance is a good choice.

In general, I personally don’t like inheritance very much, for a few reasons.

Let’s look at the code first. If we were to describe several different brands of cars, cars would be a parent class, and then each brand of car would be a subclass.

class Car {
    constructor (brand) {
        this.brand = brand
    }
    wheel () {
        return 'Four wheels'
    }
    drvie () {
        return 'It's drivable.'
    }
    addOil () {
        return 'The car can be filled with gas'
    }
}
Class OtherCar extends Car {}
Copy the code

This part of the code looks fine at the moment, realizing several basic functions of the car, we can also subclass to extend the various cars.

But now there are new energy vehicles, new energy vehicles do not need to fuel. Of course, in addition to refueling this function is not needed, the other several basic functions of the car is still needed.

If the new energy vehicle directly inherits the car this parent class, there is the first problem, gorilla and banana problem. What this problem means is that all we need is a banana, but we get a gorilla holding the banana, which we don’t need, but the parent class is forced onto the child class. Inheritance, while it is possible to override methods of a parent class, does not allow you to choose what you want to inherit.

In addition, it is difficult for a single parent class to describe all the scenes, so we may need to add several different parent classes to describe more scenes. One of the problems with inheritance is that as you scale, your code inevitably repeats.

In addition to these two problems, inheritance also has strong coupling, in that a subclass will be coupled to its parent class anyway.

Given the strong coupling, the architecture must be fragile. Once we have a problem with the design of our superclass, it can have a big impact on maintenance. Because all of the subclasses are coupled to the parent class, changing anything in the parent class might result in changing all of the subclasses.

How to solve the problem of inheritance

Inheritance is more about describing what a thing is, and a bad description will lead to all kinds of problems, so is there a way to solve these problems? The answer is a combination.

What is a combination? You can think of it as you have all kinds of parts, and you can make all kinds of products out of those parts, and composition is more about describing what one thing can do.

Now let’s take the case of the previous car and implement it by combination.

function wheel() {
  return "Four wheels";
}
function drvie() {
  return "The car is drivable.";
}
function addOil() {
  return "The car can fill up.";
}
/ / a gasoline
const car = compose(wheel, drvie, addOil)
// New energy vehicles
const energyCar = compose(wheel, drive)
Copy the code

As you can see from the above pseudocode, composition is better than inheritance. Whatever you want to describe, you can do it by combining several functions. The code is clean and reusable.

The last

In fact, the main idea of this article is the content of the following two sections. If you have any questions, please feel free to contact me in the comments section.

All of my articles in the series will be updated first on Github if you are interested. This year, I will focus on the following three columns

  • Relearn JS
  • The React advanced
  • Rewrite the component

Finally, feel the content is helpful can pay attention to my public number “front-end really fun”, there will be a lot of good things waiting for you.