preface

It has been four years since the last js inheritance series, and there are still new readers’ comments and replies from time to time. I also want to update the content, because the content at that time did not cover the ES6 extend implementation, so NOW I take time to make up for it. Of course, for those of you who are zero-based or who have forgotten about basic inheritance, you can review the first two:

Inheritance in JS

Inheritance in JS (2)

The body of the

Basic review & preliminary knowledge

To make the learning process smoother, before we start, let’s review the constructor – prototype object – instance model:

When accessing the attribute of A, it will first look for the attribute (or method) of A itself. If it cannot be found, it will find the prototype object A.prototype along the __proto__ attribute and find the corresponding attribute (or method) on the prototype object. If you can’t find it again, continue along the __proto__ of the prototype object, which is the original prototype chain we introduced.

function A (){
  this.type = 'A'
}
const a = new A();
Copy the code

Of course, we can keep looking for the prototype chain. We knowAIt’s a function, but it’s also a functionObjectAlong the__proto__Properties go up and down, and eventually they go backnull ;

a.__proto__ === A.prototype; // true
a.__proto__.__proto__ === Object.prototype; // true
a.__proto__.__proto__.__proto__ === null; // true
Copy the code

Extend source code parsing

For those of you who have studied ES6, you can extend directly with the keyword extend, for example:

// Create an Animal class
class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; };
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`); }}// Subclass Dog descends from Animal
class Dog extends Animal {
    age: number;
    constructor(name: string, age: number) { 
        super(name); 
        this.age = age;
    }
    bark() {
        console.log('Woof! Woof! '); }}const dog = new Dog('wangwang'.12);
dog.bark();// 'Woof! Woof! '
dog.move(10);//`Animal moved 10m.`
Copy the code

So what exactly does extend do? This is done by installing the typescript NPM package and running TSC locally to convert TS and ES6 code into native JS code. (of course, there is also a disadvantage of converting the code in order to achieve the simplicity of the code sometimes affect the readability, such as undefined write void 0, etc.), the above code after converting looks like this:

// The first part
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: []}instanceof Array && function (d, b) { d.__proto__ = b; }) | |function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeofb ! = ="function"&& b ! = =null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new(4)); }; }) ();// Part 2
// Create an Animal class
var Animal = / * *@class * / (function () {
    function Animal(theName) {
        this.name = theName; }; Animal.prototype.move =function (distanceInMeters) {
        if (distanceInMeters === void 0) { distanceInMeters = 0; }
        console.log("Animal moved ".concat(distanceInMeters, "m."));
    };
    returnAnimal; } ());// Part 3
// Subclass Dog descends from Animal
var Dog = / * *@class * / (function (_super) {
    __extends(Dog, _super);
    function Dog(name, age) {
        var _this = _super.call(this, name) || this;
        _this.age = age;
        return _this;
    }
    Dog.prototype.bark = function () {
        console.log('Woof! Woof! ');
    };
    Dog.prototype.move = function (distanceInMeters) {
        if (distanceInMeters === void 0) { distanceInMeters = 5; }
        console.log("Dog moved ".concat(distanceInMeters, "m."));
    };
    return Dog;
}(Animal));

// The fourth part needs no parsing
var dog = new Dog('wangwang'.12);
dog.bark(); // 'Woof! Woof! '
dog.move(10); // Dog moved 10m.

Copy the code

The code looks a bit complicated, so let’s break it down from simple to complex in the code comments:

  • Let’s look at part two firstExecute functions immediately anonymously (IIFE)It’s wrapped, and we talked about this when we talked about closures, but the nice thing about this is thatAvoid polluting the global namespace; And then inside, that’s what we talked about in canto ONEConstructor – Prototype objectThe classical model **–**attributePut it in the constructor,methodsBinding inA prototype objectUp. So this part is actually for ES6ClassCorresponding native JS writing method;
  • Part three,DogClass is written in much the same way as in Part 2, but there are a few differences:
    • _super.call(this, name), _super represents the parent class, so this step is to use the constructor of the parent class to generate an object, and then modify the object according to its own constructor.

    • The __extends method is also the core of this article.

  • And then finally, the first part, which is__extendsThe concrete implementation of. This part of the outer layer is also a simple way to avoid repeating definitions as wellAnonymous Execute Functions now (IIFE)This point will not be repeated. The core of this isextendStaticsThe implementation of the:
    • First of allObject.setPrototypeOfThis method, which redefines the prototype for an object, is used as follows:
Object.setPrototypeOf(d, b) // equivalent to d.__proto__ = b;
Copy the code

Behind each subsequent | | separator can be understood as a polyfill wording, just in order to compatible with different execution environment; * Next returns a new function. As mentioned earlier, the direct conversion can be a bit opaque, so I’ll write it in a slightly more readable form:

return function (d, b) {
    // Throw an error if b is not a constructor or null
        if (typeofb ! = ="function"&& b ! = =null) {
       	throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        }
    
        // Modify the d prototype chain pointing
        extendStatics(d, b); 
    
        // Simulate the inheritance of the prototype chain
    	function Temp() { this.constructor = d; }
          if(b === null){
                d.prototype = {}; // object.create (null) Returns a new empty Object {}
        } else{Temp. Prototype = b.protoType;var temp = newTemp(); D.p rototype = temp; }}; The first one here`if`Relatively easy to understand, not much explanation;Copy the code

The effect is d.__proto__ = b;

Then is more interesting, in order to facilitate everyone to understand, or draw the relevant diagram:

First of all, D and B are independent (of course) please note here!! , we use capital letters B and D to represent the constructors of B and D, and B and D may also be a function themselves, and also have their own corresponding prototype object, which is not shown in the diagram. (Students who don’t look well in the eyes or are not careful must be careful or they may easily make mistakes.)

Animal.__proto__ = Function. Prototype but at the same time, Animal has its own prototype object Animal. Protptype:

After extendStatics(d, b) is implemented, the prototype relationship is as follows (d’s constructor and prototype object become inaccessible, so they are gray) :

function Temp() { this.constructor = d; } prototype = b.protoType;var temp = newTemp(); D.p rototype = temp;Copy the code

The structure diagram is as follows:After executing the following code:

As you can see from the diagram, the temporary variable temp eventually becomes a prototype object for D and an instance of B. This is similar to the prototype chain inheritance we first learned, except that there is a d.__proto__ = b.

So, if var dog = new dog (‘wangwang’, 12); In fact, Dog here corresponds to D in the figure above. The prototype chain of Dog is actually dog.__proto__ === temp, and then upward is B.prototype, so the method defined in B.prototype can be called naturally.

Self-test link

So after extend, answer a few questions to test your understanding.

Q1: First, how are attributes inherited, and how are they different from ES5?

A1: extend creates the initial object by calling the methods of the parent class, and then adjusts the object according to the constructor of the subclass. ES5 inheritance (composite inheritance) essentially creates an instance object of a subclass, this, and then adds attributes of the parent class to this using call or apply.

Q2: How does dog call the move method?

Move (not present) > dog.__proto__ (temp variable).move (not present) > dog.__proto__.__proto__. Move (found)

Q3: What does the extra D. __proto__ = b do?

A3: Static methods can be inherited from the parent class, e.g. adding methods: animail.sayHello = function() {console.log(‘hello’)}; Dog.sayHello() does not exist > d.__proto__. Hello(found)

summary

This article is the inheritance of the series of subsequent articles, mainly for ES6 Extend to do a simple source analysis and principle introduction, the most critical or prototype chain graphic part, I hope to be helpful to readers.

Welcome everyone to pay attention to the column, but also hope that everyone for the favorite article, can not hesitate to like and collect, for the writing style and content of any opinion, welcome private communication.

(Want to foreign partners welcome private letter or add home page contact information consultation details ~)