[ES6] Ruan Yifeng ES6 Learning Class Inheritance

1. Concept

ClassInheritance is achieved through extendskeywords, allowing subclasses to inherit the properties and methods of the parent class. The way of writing extends is much clearer and more convenient than ES5's prototype chain inheritance.

// Point是父类,ColorPoint是子类,它通过extends关键字,继承了Point类的所有属性和方法。
class Point {
    
    
}

class ColorPoint extends Point {
    
    
}

But since no code is deployed, the two classes are exactly the same, which is equivalent to copying a Pointclass.

ES6It is stipulated that the subclass must constructor()be called in the method super(), otherwise an error will be reported.

class Point {
    
     /* ... */ }

class ColorPoint extends Point {
    
    
  constructor(x, y, color) {
    
    
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    
    
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

constructor()Keywords toString()appear both within methods and within methods. Here it represents the constructor of the parent class, which is used to create an instance object of the parent class.supersuper

Why superkeywords are needed: This is because the this object of the subclass must first be shaped through the constructor of the parent class to obtain the same instance attributes and methods as the parent class, and then process it to add its own instance of the subclass properties and methods. If super()the method is not called, the subclass cannot get its own thisobject.

class Point {
    
     /* ... */ }

class ColorPoint extends Point {
    
    
  constructor() {
    
    
  }
}

let cp = new ColorPoint(); // ReferenceError

In the above code, ColorPointthe parent class is inherited Point, but its constructor is not called super(), resulting in an error when creating a new instance.

Note: When creating a new subclass instance, the constructor of the parent class must be run once first.

Come on little plum

class Foo {
    
    
  constructor() {
    
    
    console.log(1);
  }
}

class Bar extends Foo {
    
    
  constructor() {
    
    
    super();
    console.log(2);
  }
}

const bar = new Bar();
// 1
// 2

2. super keyword

superThis keyword can be used either as a function or as an object. In both cases it is used quite differently.

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

class A {
    
    }

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

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

Notice: superAlthough it represents the constructor of the parent class A, it returns an instance of the subclass B, that is, the superinternal one thisrefers to the instance of B, so super()it is equivalent to

A.prototype.constructor.call(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.targetit points to the function currently being executed. It can be seen that when super()executed, it points to the constructor of the subclass B, not the constructor of the parent class A. In other words, super()the internal this points to B.

When used as a function, super()it can only be used in the constructor of a subclass, and an error will be reported if it is used in other places.

class A {
    
    }

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

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

In the second case, superas an object, in a normal method, it points to the prototype object of the parent class; in a static method, 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, in the subclass B super.p(), it will superbe used as an object. At this time, superin the ordinary method, pointing A.prototype, so super.p()it is equivalent A.prototype.p().

It should be noted here that because superit points to the prototype object of the parent class, the methods or properties defined on the parent class instance cannot be called 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 an attribute of the parent class A instance, super.pso it cannot be referenced.

If the attribute is defined on the prototype object of the parent class, superit can be obtained.

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, attribute x is defined above A.prototype, so super.xits value can be obtained.

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

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, super.print()although it is called A.prototype.print(), A.prototype.print()the internal this points to the instance of subclass B, resulting in the output of 2 instead of 1. That is, what is actually executed is super.print.call(this).

Since this points to the subclass instance, if you superassign a value to a certain attribute, then superthe thisassigned attribute will become the attribute of the subclass instance.

3. Inheritance of native constructors

Native constructors refer to language built-in constructors, usually used to generate data structures. ECMAScriptThe native constructors of are roughly as follows.

  • Boolean()
  • Number()
  • String()
  • Array()
  • Date()
  • Function()
  • RegExp()
  • Error()
  • Object()

Previously, these native constructors could not be inherited, for example, you could not define a Arraysubclass of your own.

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

MyArray.prototype = Object.create(Array.prototype, {
    
    
  constructor: {
    
    
    value: MyArray,
    writable: true,
    configurable: true,
    enumerable: true
  }
});

The above code defines an inherited Arrayclass MyArray. However, the behavior of this class is Arraycompletely inconsistent with

var colors = new MyArray();
colors[0] = "red";
colors.length  // 0

colors.length = 0;
colors[0]  // "red"

This happens because subclasses have no access to the native constructor's internal properties, either by passing Array.apply()or assigning to the prototype object. The native constructor ignores applywhat is passed in by the method this, that is to say, the native constructor thiscannot be bound, resulting in the inability to obtain internal properties.
ES5 creates the instance object this of the subclass first, and then adds the properties of the parent class to the subclass. Since the internal properties of the parent class cannot be obtained, the original constructor cannot be inherited. For example, the Array constructor has an internal property [[DefineOwnProperty]], which is used to update the length property when defining a new property. This internal property cannot be obtained in the subclass, causing the length property of the subclass to behave abnormally.

In the following example, we want an ordinary object to inherit from Errorobject.

var e = {
    
    };

Object.getOwnPropertyNames(Error.call(e))
// [ 'stack' ]

Object.getOwnPropertyNames(e)
// []

In the above code, we want to Error.call(e)make the ordinary object e have Errorthe instance attribute of the object through this writing method. However, Error.call()the first argument passed in is completely ignored and a new object is returned, eunchanged itself. This proves Error.call(e)that this way of writing cannot inherit native constructors.

ES6It is allowed to inherit native constructors to define subclasses, because ES6the instance object of the parent class is created first this, and then modified with the constructor of the subclass this, so that all behaviors of the parent class can be inherited. Below is an Arrayexample of inheritance.

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

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

arr.length = 0;
arr[0] // undefined

The above code defines a MyArrayclass that inherits Arraythe constructor, so you can MyArraygenerate an instance of the array from it. This means that ES6it is possible to customize subclasses of native data structures (eg Array, , etc.), which cannot be done with .StringES5

The above example also shows that extendskeywords can be used not only to inherit classes, but also to inherit native constructors. Therefore, you can define your own data structure on the basis of the native data structure. The following is the definition of an array with a version function.

class VersionedArray extends Array {
    
    
  constructor() {
    
    
    super();
    this.history = [[]];
  }
  commit() {
    
    
    this.history.push(this.slice());
  }
  revert() {
    
    
    this.splice(0, this.length, ...this.history[this.history.length - 1]);
  }
}

var x = new VersionedArray();

x.push(1);
x.push(2);
x // [1, 2]
x.history // [[]]

x.commit();
x.history // [[], [1, 2]]

x.push(3);
x // [1, 2, 3]
x.history // [[], [1, 2]]

x.revert();
x // [1, 2]

In the above code, a method VersionedArraywill be used committo generate a version snapshot of its current state and store it in historythe property. revertmethod to reset the array to the last saved version. In addition, VersionedArrayit is still an ordinary array, and all native array methods can be called on it.

The following is an example of a custom Errorsubclass that can be used to customize the behavior when an error is reported.

class ExtendableError extends Error {
    
    
  constructor(message) {
    
    
    super();
    this.message = message;
    this.stack = (new Error()).stack;
    this.name = this.constructor.name;
  }
}

class MyError extends ExtendableError {
    
    
  constructor(m) {
    
    
    super(m);
  }
}

var myerror = new MyError('ll');
myerror.message // "ll"
myerror instanceof Error // true
myerror.name // "MyError"
myerror.stack
// Error
//     at MyError.ExtendableError
//     ...

Note that subclasses that inherit from Object have a behavioral difference.

class NewObj extends Object{
    
    
  constructor(){
    
    
    super(...arguments);
  }
}
var o = new NewObj({
    
    attr: true});
o.attr === true  // false

In the above code, NewObjit is inherited Object, but it cannot pass parameters superto the parent class through the method Object. This is because the behavior of the constructor ES6has been changed . Once it is found that the method is not called in this form, the constructor will ignore the parameters.ObjectObjectnew Object()ES6Object

Guess you like

Origin blog.csdn.net/Bon_nenul/article/details/128288689