Class inheritance
1. Concept
Class
Inheritance is achieved through extends
keywords, 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 Point
class.
ES6
It 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.super
super
Why super
keywords 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 this
object.
class Point {
/* ... */ }
class ColorPoint extends Point {
constructor() {
}
}
let cp = new ColorPoint(); // ReferenceError
In the above code, ColorPoint
the 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
super
This keyword can be used either as a function or as an object. In both cases it is used quite differently.
In the first case, super
when called as a function, it represents the constructor of the parent class. ES6 requires that the constructor of the subclass must execute super
the 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: super
Although it represents the constructor of the parent class A, it returns an instance of the subclass B, that is, the super
internal one this
refers 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.target
it 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, super
as 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 super
be used as an object. At this time, super
in the ordinary method, pointing A.prototype
, so super.p()
it is equivalent A.prototype.p()
.
It should be noted here that because super
it 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.p
so it cannot be referenced.
If the attribute is defined on the prototype object of the parent class, super
it 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.x
its 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 super
assign a value to a certain attribute, then super
the this
assigned 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. ECMAScript
The 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 Array
subclass 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 Array
class MyArray
. However, the behavior of this class is Array
completely 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 apply
what is passed in by the method this
, that is to say, the native constructor this
cannot 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 Error
object.
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 Error
the 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, e
unchanged itself. This proves Error.call(e)
that this way of writing cannot inherit native constructors.
ES6
It is allowed to inherit native constructors to define subclasses, because ES6
the 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 Array
example 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 MyArray
class that inherits Array
the constructor, so you can MyArray
generate an instance of the array from it. This means that ES6
it is possible to customize subclasses of native data structures (eg Array
, , etc.), which cannot be done with .String
ES5
The above example also shows that extends
keywords 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 VersionedArray
will be used commit
to generate a version snapshot of its current state and store it in history
the property. revert
method to reset the array to the last saved version. In addition, VersionedArray
it is still an ordinary array, and all native array methods can be called on it.
The following is an example of a custom Error
subclass 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, NewObj
it is inherited Object
, but it cannot pass parameters super
to the parent class through the method Object
. This is because the behavior of the constructor ES6
has been changed . Once it is found that the method is not called in this form, the constructor will ignore the parameters.Object
Object
new Object()
ES6
Object