herencia de clase
1. Concepto
Class
La herencia se logra a través de extends
palabras clave, lo que permite que las subclases hereden las propiedades y métodos de la clase principal. La forma de escribir extensiones es mucho más clara y conveniente que la herencia de cadena prototipo de ES5.
// Point是父类,ColorPoint是子类,它通过extends关键字,继承了Point类的所有属性和方法。
class Point {
}
class ColorPoint extends Point {
}
Pero como no se implementa ningún código, las dos clases son exactamente iguales, lo que equivale a copiar una Point
clase.
ES6
Se estipula que la subclase debe constructor()
llamarse en el método super()
, de lo contrario, se informará un error.
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()
Las palabras clave toString()
aparecen tanto dentro de los métodos como dentro de los métodos. Aquí representa el constructor de la clase principal, que se utiliza para crear un objeto de instancia de la clase principal.super
super
Por qué super
se necesitan palabras clave: esto se debe a que este objeto de la subclase primero debe moldearse a través del constructor de la clase principal para obtener los mismos atributos y métodos de instancia que la clase principal, y luego procesarlo para agregar su propia instancia de la subclase. propiedades y métodos. Si super()
no se llama al método, la subclase no puede obtener su propio this
objeto.
class Point {
/* ... */ }
class ColorPoint extends Point {
constructor() {
}
}
let cp = new ColorPoint(); // ReferenceError
En el código anterior, ColorPoint
la clase principal se hereda Point
, pero no se llama a su constructor super()
, lo que genera un error al crear una nueva instancia.
Nota: Al crear una nueva instancia de subclase, el constructor de la clase principal debe ejecutarse primero una vez.
Vamos pequeña ciruela
class Foo {
constructor() {
console.log(1);
}
}
class Bar extends Foo {
constructor() {
super();
console.log(2);
}
}
const bar = new Bar();
// 1
// 2
2. súper palabra clave
super
Esta palabra clave se puede utilizar como función o como objeto. En ambos casos se utiliza de manera muy diferente.
En el primer caso, super
cuando se llama como función, representa el constructor de la clase padre. ES6 requiere que el constructor de la subclase ejecute super
la función una vez.
class A {
}
class B extends A {
constructor() {
super();
}
}
En el código anterior, entre los constructores de la subclase B super()
, significa llamar al constructor de la clase principal. Esto es necesario, de lo contrario JavaScript
el motor informará un error.
Aviso: super
Aunque representa el constructor de la clase padre A, devuelve una instancia de la subclase B, es decir, la super
interna this
hace referencia a la instancia de B, por lo super()
que equivale a
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
En el código anterior, new.target
apunta a la función que se está ejecutando actualmente. Se puede ver que cuando super()
se ejecuta, apunta al constructor de la subclase B, no al constructor de la clase padre A. En otras palabras, super()
el interno esto apunta a B.
Cuando se usa como una función, super()
solo se puede usar en el constructor de una subclase, y se informará de un error si se usa en otros lugares.
class A {
}
class B extends A {
m() {
super(); // 报错
}
}
En el código anterior, super()
si se usa en el método m de la clase B, provocará un error de sintaxis.
En el segundo caso, super
como objeto, en un método normal, apunta al objeto prototipo de la clase padre; en un método estático, apunta a la clase padre.
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
En el código anterior, en la subclase B super.p()
, se super
usará como un objeto. En este momento, super
en el método ordinario, apuntando A.prototype
, por lo que super.p()
es equivalente A.prototype.p()
.
Cabe señalar aquí que debido a que super
apunta al objeto prototipo de la clase principal, los métodos o propiedades definidos en la instancia de la clase principal no se pueden llamar super
.
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
En el código anterior, p es un atributo de la instancia de la clase principal A, super.p
por lo que no se puede hacer referencia a ella.
Si el atributo está definido en el objeto prototipo de la clase principal, super
se puede obtener.
class A {
}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
console.log(super.x) // 2
}
}
let b = new B();
En el código anterior, el atributo x se define arriba A.prototype
, por lo que super.x
se puede obtener su valor.
ES6 estipula que al llamar al método de la clase padre a través de super en el método normal de la subclase, este dentro del método apunta a la instancia de la subclase actual.
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
En el código anterior, super.print()
aunque se llama A.prototype.print()
, A.prototype.print()
el interno apunta a la instancia de la subclase B, lo que da como resultado una salida de 2 en lugar de 1. Es decir, lo que realmente se ejecuta es super.print.call(this)
.
Dado que esto apunta a la instancia de la subclase, si super
asigna un valor a un determinado atributo, super
el this
atributo asignado se convertirá en el atributo de la instancia de la subclase.
3. Herencia de constructores nativos
Los constructores nativos se refieren a los constructores integrados en el lenguaje, generalmente utilizados para generar estructuras de datos. ECMAScript
Los constructores nativos de son más o menos los siguientes.
- booleano()
- Número()
- Cadena()
- Formación()
- Fecha()
- Función()
- expresión regular()
- Error()
- Objeto()
Anteriormente, estos constructores nativos no se podían heredar, por ejemplo, no se podía definir una Array
subclase propia.
function MyArray() {
Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
});
El código anterior define una Array
clase heredada MyArray
. Sin embargo, el comportamiento de esta clase es Array
completamente inconsistente con
var colors = new MyArray();
colors[0] = "red";
colors.length // 0
colors.length = 0;
colors[0] // "red"
Esto sucede porque las subclases no tienen acceso a las propiedades internas del constructor nativo, ya sea pasándolas Array.apply()
o asignándolas al objeto prototipo. El constructor nativo ignora apply
lo que pasa por el método this
, es decir, el constructor nativo this
no se puede vincular, lo que resulta en la incapacidad de obtener propiedades internas.
ES5 crea primero el objeto de instancia this de la subclase y luego agrega las propiedades de la clase principal a la subclase. Dado que las propiedades internas de la clase principal no se pueden obtener, el constructor original no se puede heredar. Por ejemplo, el constructor Array tiene una propiedad interna [[DefineOwnProperty]], que se utiliza para actualizar la propiedad de longitud al definir una nueva propiedad. Esta propiedad interna no se puede obtener en la subclase, lo que hace que la propiedad de longitud de la subclase se comporte de forma anormal. .
En el siguiente ejemplo, queremos que un objeto ordinario herede de Error
object.
var e = {
};
Object.getOwnPropertyNames(Error.call(e))
// [ 'stack' ]
Object.getOwnPropertyNames(e)
// []
En el código anterior, queremos Error.call(e)
hacer que el objeto ordinario e tenga Error
el atributo de instancia del objeto a través de este método de escritura. Sin embargo, Error.call()
el primer argumento pasado se ignora por completo y se devuelve un nuevo objeto, e
sin cambios. Esto prueba Error.call(e)
que esta forma de escribir no puede heredar constructores nativos.
ES6
Se permite heredar constructores nativos para definir subclases, porque ES6
el objeto de instancia de la clase principal se crea primero this
y luego se modifica con el constructor de la subclase this
, de modo que todos los comportamientos de la clase principal se pueden heredar. A continuación se muestra un Array
ejemplo de herencia.
class MyArray extends Array {
constructor(...args) {
super(...args);
}
}
var arr = new MyArray();
arr[0] = 12;
arr.length // 1
arr.length = 0;
arr[0] // undefined
El código anterior define una MyArray
clase que hereda Array
el constructor, por lo que puede MyArray
generar una instancia de la matriz a partir de él. Esto significa que ES6
es posible personalizar subclases de estructuras de datos nativas (p. ej Array
., , etc.), lo que no se puede hacer con .String
ES5
El ejemplo anterior también muestra que extends
las palabras clave se pueden usar no solo para heredar clases, sino también para heredar constructores nativos. Por lo tanto, puede definir su propia estructura de datos sobre la base de la estructura de datos nativa. La siguiente es la definición de una matriz con una función de versión.
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]
En el código anterior, VersionedArray
se usará un método commit
para generar una instantánea de la versión de su estado actual y almacenarla en history
la propiedad. revert
método para restablecer la matriz a la última versión guardada. Además, VersionedArray
sigue siendo una matriz común y se pueden llamar todos los métodos de matriz nativos.
El siguiente es un ejemplo de una Error
subclase personalizada que se puede usar para personalizar el comportamiento cuando se informa un error.
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
// ...
Tenga en cuenta que las subclases que heredan de Object tienen una diferencia de comportamiento.
class NewObj extends Object{
constructor(){
super(...arguments);
}
}
var o = new NewObj({
attr: true});
o.attr === true // false
En el código anterior, NewObj
se hereda Object
, pero no puede pasar parámetros super
a la clase principal a través del método Object
. Esto se debe a que el comportamiento del constructor ES6
ha cambiado Una vez que se encuentra que el método no se llama de esta forma, el constructor ignorará los parámetros.Object
Object
new Object()
ES6
Object