class inheritance

class inheritance

1 Introduction

Class can be inherited through the extends keyword, which is much clearer and more convenient than ES5's inheritance by modifying the prototype chain.

class Point {
}

class ColorPoint extends Point {
}
 

The above code defines a ColorPoint class, which inherits all the properties and methods of the Point class through the extends keyword. But since no code is deployed, these two classes are exactly the same, which is equivalent to copying a Point class. Next, we add code inside ColorPoint.

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);// constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString();// toString()
  }
}
 

In the above code, the super keyword appears in both the constructor method and the toString method. It represents the constructor of the parent class and is used to create the this object of the parent class.

The subclass must call the super method in the constructor method, otherwise an error will be reported when the instance is created. This is because the subclass s own this object must first be shaped by the parent class s constructor to obtain the same instance properties and methods as the parent class, and then process it, plus the subclass s own instance properties and methods. If the super method is not called, the subclass will not get the this object.

class Point {/* ... */}

class ColorPoint extends Point {
  constructor() {
  }
}

let cp = new ColorPoint();//ReferenceError
 

In the above code, ColorPoint inherits the parent class Point, but its constructor does not call the super method, resulting in an error when creating an instance.

The essence of ES5 inheritance is to create an instance object this of the subclass first, and then add the method of the parent class to this (Parent.apply(this)). The inheritance mechanism of ES6 is completely different. The essence is to first add the properties and methods of the instance object of the parent class to this (so the super method must be called first), and then use the constructor of the subclass to modify this.

If the subclass does not define a constructor method, this method will be added by default. The code is as follows. In other words, no matter whether it is explicitly defined or not, any subclass has a constructor method.

class ColorPoint extends Point {
}

// 
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}
 

Another thing to note is that in the constructor of the subclass, you can use the this keyword only after calling super, otherwise an error will be reported. This is because the construction of the subclass instance is based on the super class instance, and only the super method can call the super class instance.

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color;//ReferenceError
    super(x, y);
    this.color = color;// 
  }
}
 

In the above code, the this keyword is used before the constructor method of the subclass calls super, the result is an error, and it is correct after the super method.

Below is the code to generate an instance of the subclass.

let cp = new ColorPoint(25, 8, 'green');

cp instanceof ColorPoint//true
cp instanceof Point//true
 

In the above code, the instance object cp is an instance of the ColorPoint and Point classes at the same time, which is exactly the same as the behavior of ES5.

Finally, the static methods of the parent class will also be inherited by the child class.

class A {
  static hello() {
    console.log('hello world');
  }
}

class B extends A {
}

B.hello() //hello world
 

In the above code, hello() is a static method of class A, and B inherits A and also inherits the static method of A.

super keyword

The super keyword can be used as a function or as an object. In these two cases, its usage is completely different.

In the first case, when super is called as a function, it represents the constructor of the parent class. ES6 requires that the constructor of a subclass must execute the super function once.

class A {}

class B extends A {
  constructor() {
    super();
  }
}
 

In the above code, the super() in the constructor of subclass B represents calling the constructor of the parent class. This is necessary, otherwise the JavaScript engine will report an error.

Note that although super represents the constructor of the parent class A, it returns an instance of the subclass B, that is, this inside super refers to an instance of B, so super() is equivalent to A.prototype.constructor.call here. (this).

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A()//A
new B()//B
 

In the above code, new.target points to the currently executing function. As you can see, when super() is executed, it points to the constructor of the subclass B, not the constructor of the parent class A. In other words, this inside super() points to B.

As a function, super() can only be used in the constructor of a subclass, and it will report an error when used in other places.

class A {}

class B extends A {
  m() {
    super();// 
  }
}
 

In the above code, super() is used in the m method of class B, which will cause a syntax error.

In the second case, when super is used as an object, in ordinary methods, it points to the prototype object of the parent class; in static methods, it points to the parent class.

class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p());//2
  }
}

let b = new B();
 

In the above code, super.p() in subclass B uses super as an object. At this time, super is in the ordinary method, pointing to A.prototype, so super.p() is equivalent to A.prototype.p().

It should be noted here that since super points to the prototype object of the parent class, the methods or properties defined on the parent class instance cannot be called by super.

class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m//undefined
 

In the above code, p is the attribute of the parent class A instance, super.p cannot reference it.

If the attribute is defined on the prototype object of the parent class, super can get it.

class A {}
A.prototype.x = 2;

class B extends A {
  constructor() {
    super();
    console.log(super.x)//2
  }
}

let b = new B();
 

In the above code, the property x is defined on A.prototype, so super.x can get its value.

ES6 stipulates that when the method of the parent class is called through super in the ordinary method of the subclass, the this inside the method points to the current instance of the subclass.

class A {
  constructor() {
    this.x = 1;
  }
  print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print();
  }
}

let b = new B();
b.m()//2
 

In the above code, although super.print() calls A.prototype.print(), the this inside A.prototype.print() points to an instance of subclass B, resulting in the output of 2 instead of 1. In other words, super.print.call(this) is actually executed.

Since this points to a subclass instance, if a property is assigned via super, then super is this, and the assigned property will become the property of the subclass instance.

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x);//undefined
    console.log(this.x);//3
  }
}

let b = new B();
 

In the above code, super.x is assigned a value of 3, which is equivalent to assigning a value of 3 to this.x. When reading super.x, A.prototype.x is read, so undefined is returned.

If super is used as an object in a static method, then super will point to the parent class instead of the prototype object of the parent class.

class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }

  myMethod(msg) {
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg);
  }

  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1);//static 1

var child = new Child();
child.myMethod(2);//instance 2
 

In the above code, super points to the parent class in the static method, and points to the prototype object of the parent class in the ordinary method.

In addition, when the method of the parent class is called through super in the static method of the subclass, the this inside the method points to the current subclass, not the instance of the subclass.

class A {
  constructor() {
    this.x = 1;
  }
  static print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  static m() {
    super.print();
  }
}

B.x = 3;
B.m()//3
 

In the above code, in the static method Bm, super.print points to the static method of the parent class. The this in this method points to B, not an instance of B.

Note that when using super, you must explicitly specify whether it is used as a function or as an object, otherwise an error will be reported.

class A {}

class B extends A {
  constructor() {
    super();
    console.log(super);// 
  }
}
 

In the above code, the super in console.log(super) cannot be seen whether it is used as a function or as an object, so the JavaScript engine will report an error when it parses the code. At this time, if the data type of super can be clearly indicated, no error will be reported.

class A {}

class B extends A {
  constructor() {
    super();
    console.log(super.valueOf() instanceof B);//true
  }
}

let b = new B();
 

In the above code, super.valueOf() indicates that super is an object, so no error will be reported. At the same time, because super makes this point to an instance of B, super.valueOf() returns an instance of B.

Finally, because objects always inherit other objects, you can use the super keyword in any object.

var obj = {
  toString() {
    return "MyObject: " + super.toString();
  }
};

obj.toString();//MyObject: [object Object]
 

3. The prototype attribute and __proto__ attribute of the class

In the ES5 implementation of most browsers, every object has a __proto__ attribute, which points to the prototype attribute of the corresponding constructor. As the syntactic sugar of the constructor, Class has both the prototype attribute and the __proto__ attribute, so there are two inheritance chains at the same time.

(1) The __proto__ attribute of the subclass indicates the inheritance of the constructor and always points to the parent class.

(2) The __proto__ attribute of the prototype attribute of the subclass indicates the inheritance of the method and always points to the prototype attribute of the parent class.

class A {
}

class B extends A {
}

B.__proto__ === A//true
B.prototype.__proto__ === A.prototype//true
 

In the above code, the __proto__ attribute of the subclass B points to the parent class A, and the __proto__ attribute of the prototype attribute of the subclass B points to the prototype attribute of the parent class A.

This result is because the inheritance of the class is implemented in accordance with the following pattern.

class A {
}

class B {
}

//B   A  
Object.setPrototypeOf(B.prototype, A.prototype);

//B   A  
Object.setPrototypeOf(B, A);

const b = new B();
 

The chapter "Object Extensions" gave the implementation of the Object.setPrototypeOf method.

Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}
 

Therefore, the above result is obtained.

Object.setPrototypeOf(B.prototype, A.prototype);
// 
B.prototype.__proto__ = A.prototype;

Object.setPrototypeOf(B, A);
// 
B.__proto__ = A;
 

These two inheritance chains can be understood like this: as an object, the prototype (__proto__ attribute) of the subclass (B) is the parent class (A); as a constructor, the prototype object (prototype attribute) of the subclass (B) It is an instance of the prototype object (prototype attribute) of the parent class.

B.prototype = Object.create(A.prototype);
// 
B.prototype.__proto__ = A.prototype;
 

The extends keyword can be followed by multiple types of values.

class B extends A {
}
 

A in the above code can be inherited by B as long as it is a function with a prototype attribute. Since functions have prototype properties (except for Function.prototype functions), A can be any function.

Below, two cases are discussed. The first is that the subclass inherits the Object class.

class A extends Object {
}

A.__proto__ === Object//true
A.prototype.__proto__ === Object.prototype//true
 

In this case, A is actually a copy of the constructor Object, and an instance of A is an instance of Object.

In the second case, there is no inheritance.

class A {
}

A.__proto__ === Function.prototype//true
A.prototype.__proto__ === Object.prototype//true
 

In this case, as a base class (that is, there is no inheritance), A is an ordinary function, so it directly inherits Function.prototype. However, after calling A, an empty object (ie Object instance) is returned, so A.prototype.__proto__ points to the prototype property of the constructor (Object).

Proto property of the instance

The __proto__ property of the __proto__ property of the subclass instance points to the __proto__ property of the parent class instance. In other words, the prototype of the prototype of the child class is the prototype of the parent class.

var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');

p2.__proto__ === p1.__proto__//false
p2.__proto__.__proto__ === p1.__proto__//true
 

In the above code, ColorPoint inherits Point, causing the prototype of the former to be the prototype of the latter.

Therefore, through the __proto__.__proto__ attribute of the subclass instance, the behavior of the parent class instance can be modified.

p2.__proto__.__proto__.printName = function () {
  console.log('Ha');
};

p1.printName()//"Ha"
 

The above code adds a method to the Point class on the ColorPoint instance p2, and the result affects the Point instance p1.