5
头图

foreword

It has been four years since the last js inheritance series, and there are new reader comments and replies from time to time. I am also thinking about updating the content when I am happy, because the extend implementation of es6 was not involved in the content at that time, so I take the time now Fill. Of course, if you are a 0-based classmate or a classmate who has forgotten about the basic inheritance, you can review the first two articles:

Detailed explanation of inheritance in js (1)

Detailed explanation of inheritance in js (2)

text

Basic Review & Preliminary Knowledge

In order to make the later learning process smoother, before we start, let's review this constructor-prototype object-instance model:

When accessing a when the property will start a property itself (or methods) go, if not, will be along __proto__ find property prototype object A.prototype , in prototype object find the corresponding on Attribute (or method); if you can't find it again, continue __proto__ the prototype object, which is the content of the prototype chain that we introduced first.

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

图片
Of course, the prototype chain on the graph can continue to be found. We know that A is a function, its essence is also Object . It __proto__ attribute, and will eventually return null ;

a.__proto__ === A.prototype; // true
a.__proto__.__proto__ === Object.prototype; // true
a.__proto__.__proto__.__proto__ === null; // true

extend implements source code analysis

Entering the topic, students who have es6 extend , for example:

// 首先创建一个Animal类
class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; };
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}

// 子类Dog继承于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.`

So what exactly did extend ? Here, by installing the typescript npm package, and then running tsc [file path] locally, the ts and es6 codes are converted into native js codes for research, (of course, there are also native js codes for research. A disadvantage is that the converted code may sometimes affect readability in order to pursue code void 0 undefined writing 061e15ec940596, etc.), the above code after conversion looks like this:

// 第一部分
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 (typeof b !== "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 __());
    };
})();

// 第二部分
// 首先创建一个Animal类
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."));
    };
    return Animal;
}());

// 第三部分
// 子类Dog继承于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));

// 第四部分 无需解析
var dog = new Dog('wangwang', 12);
dog.bark(); // 'Woof! Woof!'
dog.move(10); // Dog moved 10m.

The code looks a bit complicated. We the content complexity of each part from simple to complex :

  • Look at the second part first, with execute the function immediately anonymous ( IIFE ) wrapped in a layer, as we said at the time of closure of the talk, the benefits of this writing is avoid contamination to the global namespace ; then in the interior, that is, before the first article said constructor - prototype object classic model - property in the constructor, method bound prototype object on, so this Part of it is actually the native js writing method corresponding to Class
  • The third part, the Dog same as the second part, but there are still several differences:

    • _super.call(this, name) and _super represent the parent class, so in this step, uses the constructor of the parent class to generate an object, and then according to its own constructor, modify the object ;
    • __extends method is also the core content of this article.

    • __extends 's introduce the first part, that is, the specific implementation of 061e15ec9408a1. The outer layer of this part is also a simple to avoid duplicate definitions and Anonymous Immediate Execution Function (IIFE) , which will not be repeated. The core content is the implementation extendStatics

      • First, Object.setPrototypeOf introduce the method 061e15ec940931. The function of this method is to re-designate the prototype for an object. The usage is as follows:

        Object.setPrototypeOf(d, b) // 等价于d.__proto__ = b;

        After each subsequent || delimiter, it can be understood as a polyfill writing method, just to be compatible with different execution environments;

      • Next, return a new function. As mentioned earlier, the direct conversion may be a bit obscure, so I will organize it into a more readable way of writing here:

        return function (d, b) {
        // 当b不是构造函数或者null时,抛出错误
          if (typeof b !== "function" && b !== null) {
             throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
          }
        
          // 修改d原型链指向
          extendStatics(d, b); 
        
          // 模拟原型链的继承
          function Temp() { this.constructor = d; }
            if(b === null){
                  d.prototype = {}; // Object.create(null) 此时返回一个新的空对象{}
          } else {
                Temp.prototype = b.prototype;
                var temp = new Temp(); 
                d.prototype = temp;
          }
        };
        此处第一个 `if` 比较好理解,不多解释;
        

The next extendStatics(d, b) also introduced the effect is d.__proto__ = b;

Then it's more interesting. In order to facilitate everyone's understanding, let's draw a related relationship diagram:

First of all, d and b are independent of each other (of course) Note here! ! ! , We use capital letters B and D to represent the constructors of b and d, and b and d themselves may also be a function, and also have their own corresponding prototype objects, but is not marked on the figure. (Students who are not very good or not very careful must be serious, otherwise it is easy to understand and make mistakes)

For example, the previous Animal corresponds to b in the B , then 061e15ec940b29 corresponds to Function , that is, Animal.__proto__ = Function.prototype , but at the same time, Animal also has its own prototype object Animal.protptype :

After executing extendStatics(d, b) , the prototype relationship is as follows (D's constructor and prototype object become inaccessible, so they are shown in gray):

After executing the following code:

function Temp() { this.constructor = d; }
Temp.prototype = b.prototype;
var temp = new Temp(); 
d.prototype = temp;

The structure diagram is as follows:

As can be seen from the figure, this temporary variable temp finally becomes the d , which is also an instance of b . prototype chain that we learned first, , the difference is that there is an d.__proto__ = b .

Then, if you execute var dog = new Dog('wangwang', 12); , the Dog here corresponds to the d , dog prototype chain in the dog.__proto__ === temp , and then it is b.prototype . Naturally, you can call the method b.prototype

self-test

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

Q1: First, how are properties inherited, and what is the difference from ES5?

A1: extend means that creates an initial object by calling the method of the parent class, and then adjusts the object according to the constructor of the subclass; ES5 inheritance (combination inheritance), the essence is to create an instance of the subclass first Object this , and then use call or apply to add the properties of the parent class to this .

Q2: dog how is the call to move method?

A2: This problem is actually the prototype chain model just analyzed earlier. The search order of the method is: dog.move (does not exist) > dog.__proto__ (temp variable).move (does not exist) > dog.__proto__.__proto__ .move (find)

Q3: extra d.__proto__ = b What is the role?

A3: You can inherit the static method of the parent class, such as adding a method: Animail.sayHello = function() {console.log('hello')}; , then Dog.sayHello() also take effect, you can refer to the above figure to understand, the search order: d.hello (does not exist) > d.__proto__.hello (found)

summary

This article is the follow-up article of the inheritance series. It mainly focuses on the ES6 in Extend . The most important part is the schematic part of the prototype chain. I hope it can be helpful to readers.

Welcome to the concern column, I hope you to favorite articles, can not hesitate to point praise and collection , for writing style and content of any comments are welcome to exchange private messages.

(Small partners who want to come to foreign companies are welcome to send a private message or add the contact information of the homepage to consult the details~)


安歌
7k 声望5.5k 粉丝

目前就职于Ringcentral厦门,随缘答题, 佛系写文章,欢迎私信探讨.