[ES6] Herencia de clase de aprendizaje de Ruan Yifeng ES6

1. Concepto

ClassLa herencia se logra a través de extendspalabras 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 Pointclase.

ES6Se 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.supersuper

Por qué superse 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 thisobjeto.

class Point {
    
     /* ... */ }

class ColorPoint extends Point {
    
    
  constructor() {
    
    
  }
}

let cp = new ColorPoint(); // ReferenceError

En el código anterior, ColorPointla 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

superEsta palabra clave se puede utilizar como función o como objeto. En ambos casos se utiliza de manera muy diferente.

En el primer caso, supercuando se llama como función, representa el constructor de la clase padre. ES6 requiere que el constructor de la subclase ejecute superla 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: superAunque representa el constructor de la clase padre A, devuelve una instancia de la subclase B, es decir, la superinterna thishace 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.targetapunta 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, supercomo 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 superusará como un objeto. En este momento, superen 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 superapunta 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.ppor lo que no se puede hacer referencia a ella.

Si el atributo está definido en el objeto prototipo de la clase principal, superse 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.xse 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 superasigna un valor a un determinado atributo, superel thisatributo 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. ECMAScriptLos 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 Arraysubclase 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 Arrayclase heredada MyArray. Sin embargo, el comportamiento de esta clase es Arraycompletamente 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 applylo que pasa por el método this, es decir, el constructor nativo thisno 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 Errorobject.

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 Errorel 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, esin cambios. Esto prueba Error.call(e)que esta forma de escribir no puede heredar constructores nativos.

ES6Se permite heredar constructores nativos para definir subclases, porque ES6el objeto de instancia de la clase principal se crea primero thisy 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 Arrayejemplo 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 MyArrayclase que hereda Arrayel constructor, por lo que puede MyArraygenerar una instancia de la matriz a partir de él. Esto significa que ES6es posible personalizar subclases de estructuras de datos nativas (p. ej Array., , etc.), lo que no se puede hacer con .StringES5

El ejemplo anterior también muestra que extendslas 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, VersionedArrayse usará un método commitpara generar una instantánea de la versión de su estado actual y almacenarla en historyla propiedad. revertmétodo para restablecer la matriz a la última versión guardada. Además, VersionedArraysigue siendo una matriz común y se pueden llamar todos los métodos de matriz nativos.

El siguiente es un ejemplo de una Errorsubclase 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, NewObjse hereda Object, pero no puede pasar parámetros supera la clase principal a través del método Object. Esto se debe a que el comportamiento del constructor ES6ha cambiado Una vez que se encuentra que el método no se llama de esta forma, el constructor ignorará los parámetros.ObjectObjectnew Object()ES6Object

Supongo que te gusta

Origin blog.csdn.net/Bon_nenul/article/details/128288689
Recomendado
Clasificación