preface

First of all, welcome everyone to pay attention to my Github blog, which can also be regarded as a little encouragement to me. After all, there is no way to make money by writing things, and it is my enthusiasm and encouragement that can persist.

I haven’t written anything for a long time. I haven’t taken the time to stick to writing for various reasons recently. I feel like running. Recently have been want to calm down and write something again, selected topic has become a gives me a headache problem, occasionally the inheritance of the JavaScript problems in work recently sometimes feel trance, realized that even very many knowledge, also need to regularly review and practice, even if again familiar things also often can make you feel strange, So let’s start the year off with a very basic article.

class

Unlike the Java language, JavaScript itself has the concept of classes. As a prototype-based language, JavaScript (recommended by my previous writing about the scope chain and ProtoType chain of JavaScript), to this day, There are still many people who don’t recommend heavy use of object-facing features in JavaScript. But for now, many front-end frameworks, such as React, have class-based concepts. To be clear, the purpose of classes is to generate objects, and generating objects in JavaScript is not as tedious as in other languages. We can easily create an object using object literals:

var person = {
    name: "MrErHu".sayName: function(){
        alert(this.name); }};Copy the code

It all seems perfect, but when we want to create an infinite number of similar objects, we find that object literals are not enough. Of course, you would be wise to use factory mode to create a series of objects:

function createObject(name){
    return {
        "name": name,
        "sayName": function(){
            alert(this.name); }}}Copy the code

However, there is a significant problem with this approach. There is no connection between the objects generated by the factory pattern and there is no way to identify the type of the object, which is where constructors come in. There is no difference between a constructor and a normal function in JavaScript, except that the constructor is called with the new operator.

function Person(name, age, job){
    this.name = name;
    this.sayName = function(){
        alert(this.name);
    };    
}

var obj = new Person();
obj.sayName();
Copy the code

We know that the new operator does the following four steps:

  1. Create a brand new object
  2. Internal properties of new objects[[Prototype]](Informal attributes__proto__) to the prototype of the constructor
  3. constructionalthisNew objects are bound
  4. If the function returns no other object, thennewThe function call in the expression automatically returns the new object

This way we can use the constructor to generate objects for type determination. The problem with the pure constructor pattern, however, is that each object’s methods are independent of each other, and functions are essentially objects, resulting in a lot of wasted memory. Recalling the third step of the new operator, the internal properties of our newly generated object [[Prototype]] are attached to the constructor’s Prototype, so we can use this property to mix constructor and Prototype patterns to solve the above problem.

function Person(name, age, job){
    this.name = name;
}

Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name); }}var obj = new Person();
obj.sayName();
Copy the code

We solved this problem by putting sayName into the prototype of the constructor so that the generated object can find the corresponding method by looking up the prototype chain using sayName. All objects share one method, even though you might think that the prototype chain lookup might take a little bit of time, In fact, this problem can be ignored with today’s JavaScript engines. For prototype modifications to constructors, there are also possible ways to handle the above:

Person.prototype.sayName = function(){
    alert(this.name);
}
Copy the code

We know that the constructor property of a function’s prototype executes the function itself. If you are replacing the original prototype with a new object and constructor is important to you, add it manually.

Object.defineProperty(Person, "constructor", {
    configurable: false.enumerable: false.writable: true.value: Person
});
Copy the code

Until now, creating a class in JavaScript would have been too much of a hassle. In fact, it’s much more than that. For example, the class we created could be called directly, polluting the global environment, for example:

Person('MrErHu');
console.log(window.name); //MrErHu
Copy the code

Things are changing, however, with THE advent of ES6, which implements the concept of classes for us in JavaScript, and all of the above code can be implemented in the introduction of classes.

class Person {
    constructor(name){
        this.name = name;
    }
    
    sayName(){
        alert(this.name); }}Copy the code

We have defined a class that we can use as before:

let person = new Person('MrErHu');
person.sayName(); //MrErHu
Copy the code

As you can see, the constructor function of the class takes over the function of the previous constructor, and instance attributes of the class can be initialized here. The class method sayName is equivalent to the prototype constructor we defined earlier. In ES6, classes are just syntactic sugar for functions:

typeof Person  //"function"
Copy the code

The classes in ES6 differ from our custom classes in a few ways compared to the way we created them above. First, there is no variable promotion for a class, so it cannot be used before it is defined:

let person = new Person('MrErHu')
class Person { / /... }
Copy the code

The above usage is incorrect. So a class is more like a function expression.

Second, all code in the class declaration is automatically run in strict mode, and the class cannot be taken out of strict mode. This means that all the code in the class declaration runs in “Use Strict”.

Furthermore, all methods in a class are not enumerable.

Finally, classes cannot be called directly and must be called through the new operator. There are internal attributes for functions [[Constructor]] and [[Call]], which of course are not accessible externally but only in the JavaScript engine. When we Call a function directly, we Call the internal property [[Call]], and all we do is execute the function body directly. When we call with the new operator, we are actually calling the internal attribute [[Constructor]], and all we do is create a new instance object, execute a function on it (bind this), and return the new instance object. Because the class has no internal attribute [[Call]], it cannot be called directly. A quick mention of the meta property new.target in ES6

Meta-attributes are non-object attributes that give us some additional information. Target is the target of the new operator when the [[Constructor]] property is called, and new.target is undefined if the [[Call]] property is called. For example, we can define a function that can only be called with the new operator:

function Person(){
    if(new.target === undefined) {throw('This function must be called with the new operator'); }}Copy the code

Or we could use JavaScript to create a function similar to the virtual function in C++ :

class Person {
  constructor() {
    if (new.target === Person) {
      throw new Error('This class cannot be instantiated'); }}}Copy the code

  

inheritance

In the absence of ES6, implementing inheritance is no small task. On the one hand we need to create the parent class’s attributes in the derived class, on the other hand we need to inherit the parent class’s methods, such as the following implementation:

function Rectangle(width, height){
  this.width = width;
  this.height = height;
}

Rectangle.prototype.getArea = function(){
  return this.width * this.height;
}

function Square(length){
  Rectangle.call(this, length, length);
}

Square.prototype = Object.create(Rectangle.prototype, {
  constructor: {
    value: Square,
    enumerable: false.writable: false.configurable: false}});var square = new Square(3);

console.log(square.getArea());
console.log(square instanceof Square);
console.log(square instanceof Rectangle);
Copy the code

Rectangle. Call (this, length, length); Rectangle. Call (this, length, length); Rectangle. We assign a new prototype to Square. In addition to using object.create, you should also see the following:

Square.prototype = new Rectangle();
Object.defineProperty(Square.prototype, "constructor", {
    value: Square,
    enumerable: false.writable: false.configurable: false
});
Copy the code

Object.create is a new method in ES5 that creates a new Object. The object being created inherits the prototype of another object, and you can specify some properties when you create a new object. Object.create specifies attributes in the same way that Object.defineProperty does, using property descriptors. As you can see, inheritance through Object.create and new is essentially the same. But ES6 can greatly simplify the steps of inheritance:

class Rectangle{
    constructor(width, height){
        this.width = width;
        this.height = height;
    }
    
    getArea(){
        return this.width * this.height; }}class Square extends Rectangle{
    construct(length){
        super(length, length); }}Copy the code

We can see how easy it is to implement class inheritance via ES6. The purpose of calling super in the constructor of Square is to call the constructor of the parent class. Of course, calling super is not necessary. If you default the constructor, it will be called automatically, passing in all the arguments. Not only that, but ES6’s class inheritance provides new features. First, extends can inherit any type of expression, as long as that expression ultimately returns an inheritable function. For example, null and generator and arrow functions do not have this property and therefore cannot be inherited. Such as:

class A{}
class B{}

function getParentClass(type){
    if(/ /...). {
        return A;
    }
    if(/ /...). {
        returnB; }}class C extends getParentClass(/ /...).{}Copy the code

As you can see, we have implemented dynamic inheritance using the above code, which can inherit different classes based on different judgment criteria. ES6 inheritance differs from ES5’s implementation of class inheritance in one other way. ES5 creates an instance of the subclass, and then creates the attributes of the parent class based on the instance of the subclass. ES6 does the opposite, creating an instance of the parent class and then extending the attributes of the child class on top of that instance. With this property we can do something that ES5 cannot: inherit native objects.

function MyArray() {
  Array.apply(this.arguments);
}

MyArray.prototype = Object.create(Array.prototype, {
  constructor: {
    value: MyArray,
    writable: true.configurable: true.enumerable: true}});var colors = new MyArray();
colors[0] = "red";
colors.length  / / 0

colors.length = 0;
colors[0]  // "red"
Copy the code

As you can see, length in an instance of MyArray inherited from the native Array does not dynamically reflect the number of elements in the Array or change the data in the Array by changing the length attribute, as does the instance of the native Array class. The reason for this is that the traditional way of implementing array inheritance is to create a subclass and then extend the attributes and methods of the parent class, so there is no related method of inheritance, but ES6 can easily implement this:

class MyArray extends Array {
  constructor(... args) {super(...args);
  }
}

var arr = new MyArray();
arr[0] = 12;
arr.length / / 1

arr.length = 0;
arr[0] // undefined
Copy the code

We can see that arrays created by the MyArray class with extends can use the length property to reflect array changes and change array elements in the same way that native arrays do. Furthermore, in ES6, we can use the symbol. species property to change the return instance type of methods that inherit from native objects when we inherit from them. For example, array.prototype. slice would have returned an instance of Array type, but by setting symbol. species we can make it return a custom object type:

class MyArray extends Array {
  static get [Symbol.species](){
    return MyArray;
  }
    
  constructor(... args) {super(...args);
  }
}

let items = new MyArray(1.2.3.4);
subitems = items.slice(1.3);

subitems instanceof MyArray; // true
Copy the code

Finally, one thing to note is that the extends implementation can inherit static member functions of its parent class, for example:

class Rectangle{
    / /...
    static create(width, height){
        return newRectangle(width, height); }}class Square extends Rectangle{
    / /...
}

let rect = Square.create(3.4);
rect instanceof Square; // true
Copy the code