la herencia de clase es una forma para que una clase extienda otra clase.

para que podamos crear una nueva funcionalidad sobre la existente.

El «extends» palabra clave

supongamos que tenemos la clase Animal:

he Aquí cómo podemos representar animal objeto y Animal clase gráficamente:

…Y nos gustaría crear otro class Rabbit.,

como los conejos son animales, Rabbit la clase debe basarse en Animal, tener acceso a métodos animales, para que los conejos puedan hacer lo que los animales» genéricos » pueden hacer.

La sintaxis para extender otra clase es: class Child extends Parent.

Vamos a crear class Rabbit que hereda de Animal:

el Objeto de Rabbit clase tienen acceso tanto a Rabbit métodos, tales como rabbit.hide(), y también a Animal métodos, tales como rabbit.run().,

internamente, extends la palabra clave funciona usando la buena mecánica de prototipos. Se establece Rabbit.prototype.] a Animal.prototype. Por lo tanto, si un método no se encuentra en Rabbit.prototype, JavaScript lo toma de Animal.prototype.,

Por ejemplo, para encontrar rabbit.run método, el motor de cheques (de abajo hacia arriba en la imagen):

Como podemos recordar el capítulo prototipos Nativos, JavaScript utiliza prototípico de la herencia de los objetos incorporados. Por ejemplo, Date.prototype.]es Object.prototype. Es por eso que las fechas tienen acceso a métodos de objetos genéricos.,

Cualquier expresión es permitido después de extends

la Clase de sintaxis permite especificar no sólo de una clase, pero cualquier expresión después de extends.

Por ejemplo, una llamada a una función que genera la clase padre:

function f(phrase) { return class { sayHi() { alert(phrase); } };}class User extends f("Hello") {}new User().sayHi(); // Hello

Aquí class User hereda el resultado de f("Hello").,

que puede ser útil para patrones de programación avanzados cuando usamos funciones para generar clases dependiendo de muchas condiciones y podemos heredar de ellas.

sobreescribir un método

ahora vamos a avanzar y sobreescribir un método. De forma predeterminada, todos los métodos que no se especifican en class Rabbit se toman directamente «tal cual» desde class Animal.,

Pero si especificamos nuestro propio método de Rabbit, como stop() luego será usado en lugar de:

class Rabbit extends Animal { stop() { // ...now this will be used for rabbit.stop() // instead of stop() from class Animal }}

Generalmente no queremos reemplazar por completo al padre, sino a construir en la parte superior de la misma para modificar o ampliar su funcionalidad. Hacemos algo en nuestro método, pero llamamos al método padre antes/después de él o en el proceso.

Las clases proporcionan "super" palabra clave para eso.

  • super.method(...) para llamar a un método padre.,
  • super(...) para llamar a un constructor padre (solo dentro de nuestro constructor).

por ejemplo, deje que nuestro conejo se autohide cuando se detiene:

Ahora Rabbittiene el método stopque llama al Padre super.stop() en el proceso.

Overriding constructor

Con Los Constructores se vuelve un poco complicado.

Hasta ahora, Rabbit no tiene su propio constructor.,

de Acuerdo a la especificación, si una clase se extiende a otra clase y no tiene constructor, entonces el siguiente «vacío» constructor se genera:

class Rabbit extends Animal { // generated for extending classes without own constructors constructor(...args) { super(...args); }}

Como podemos ver, básicamente, las llamadas de los padres constructor pasar todos los argumentos. Eso sucede si no escribimos un constructor propio.

Ahora vamos a añadir un constructor personalizado a Rabbit. Se especificará el earLength además name:

¡Vaya!, Tenemos un error. Ahora no podemos crear conejos. ¿Qué salió mal?

La respuesta corta es:

  • Los Constructores en clases heredadas deben llamar a super(...), y (!) hacerlo antes de usar this.

But pero ¿por qué? ¿Qué está pasando aquí? De hecho, el requisito parece extraño.

por supuesto, hay una explicación. Vamos a entrar en detalles, para que realmente entiendas lo que está pasando.

en JavaScript, hay una distinción entre una función constructora de una clase heredera (el llamado «constructor derivado») y otras funciones., Un constructor derivado tiene una propiedad interna especial ]:"derived". Es una etiqueta interna especial.

esa etiqueta afecta su comportamiento con new.

  • cuando se ejecuta una función regular con new, crea un objeto vacío y lo asigna a this.
  • Pero cuando se ejecuta un constructor derivado, no hace esto. Espera que el constructor padre haga este trabajo.,

así que un constructor derivado debe llamar a super para ejecutar su constructor padre (base), de lo contrario no se creará el objeto para this. Y obtendremos un error.

para que el constructor Rabbit funcione, necesita llamar a super() antes de usar this, como aquí:

invalidando los campos de clase: una nota difícil

div>

esta nota asume que tienes cierta experiencia con clases, tal vez en otros lenguajes de programación.,

Proporciona una mejor comprensión del lenguaje y también explica el comportamiento que podría ser una fuente de errores (pero no muy a menudo).

si le resulta difícil de entender, simplemente continúe, continúe leyendo y luego vuelva a él algún tiempo después.

podemos anular no solo los métodos, sino también los campos de clase.

aunque, hay un comportamiento complicado cuando accedemos a un campo sobreescrito en el constructor padre, bastante diferente de la mayoría de los otros lenguajes de programación.,

Considere este ejemplo:

Aquí, clase de Rabbit amplía Animal y reemplaza name campo con su propio valor.

no hay constructor propio en Rabbit, por lo que se llama al constructor Animal.

Lo interesante es que en ambos casos: new Animal() y new Rabbit(), el alert en la línea (*) muestra animal.,

En otras palabras, el constructor padre siempre usa su propio valor de campo, no el invalidado.

¿Qué tiene de extraño?

Si aún no está claro, compare con los métodos.

Aquí está el mismo código, pero en lugar de this.name campo que llamamos this.showName() método:

tenga en cuenta: ahora la salida es diferente.

y eso es lo que naturalmente esperamos. Cuando se llama al constructor padre en la clase derivada, utiliza el método sobreescrito.

But pero para los campos de clase no es así. Como se ha dicho, el constructor padre siempre usa el campo Padre.,

¿por Qué existe la diferencia?

bueno, la razón está en el orden de inicialización del campo. El campo class se inicializa:

  • Antes del constructor para la clase base (que no extiende nada),
  • inmediatamente después de super() para la clase derivada.

En nuestro caso, Rabbit es la clase derivada. No hay constructor() en él. Como se dijo anteriormente, es lo mismo que si hubiera un constructor vacío con solo super(...args).,

So,new Rabbit()callssuper(), ejecutando así el constructor padre, y (por la regla para las clases derivadas) solo después de que se inicialicen sus campos de clase. En el momento de la ejecución del constructor padre, todavía no hay campos de clase Rabbit, por eso se usan campos Animal.

esta sutil diferencia entre campos y métodos es específica de JavaScript

afortunadamente, este comportamiento solo se revela si se usa un campo sobreescrito en el constructor padre., Entonces puede ser difícil entender lo que está pasando, así que lo estamos explicando aquí.

si se convierte en un problema, uno puede solucionarlo usando métodos o getters/setters en lugar de campos.

Super: internals,]

información avanzada

Si está leyendo el tutorial por primera vez, esta sección puede omitirse.

se trata de los mecanismos internos detrás de la herencia y super.

Vamos a llegar un poco más bajo el capó de super. Veremos algunas cosas interesantes en el camino.,

en primer lugar, de todo lo que hemos aprendido hasta ahora, es imposible que super funcione en absoluto!

sí, de hecho, preguntémonos, ¿cómo debería funcionar técnicamente? Cuando se ejecuta un método de objeto, obtiene el objeto actual como this. Si llamamos a super.method()entonces, el motor necesita obtener el method del prototipo del objeto actual. ¿Pero cómo?

la tarea puede parecer simple, pero no lo es., El motor conoce el objeto actual this, por lo que podría obtener el padre method como this.__proto__.method. Desafortunadamente, una solución tan «ingenua» no funcionará.

Vamos a demostrar el problema. Sin clases, usando objetos simples En aras de la simplicidad.

Puede omitir esta parte e ir a la subsección ] si no desea conocer los detalles. Eso no hará daño. O siga leyendo si está interesado en entender las cosas en profundidad.

en el ejemplo siguiente, rabbit.__proto__ = animal., Ahora intentemos: en rabbit.eat() llamaremos a animal.eat(), usando this.__proto__:

en la línea (*) tomamos eat desde el prototipo (animal) y llámelo en el contexto del objeto actual. Tenga en cuenta que .call(this) es importante aquí, porque un simple this.__proto__.eat() ejecutaría parent eat en el contexto del prototipo, no en el objeto actual.,

y en el código anterior realmente funciona como se pretende: tenemos el correcto alert.

Ahora vamos a añadir un objeto más a la cadena. Veremos cómo se rompen las cosas:

¡el código ya no funciona! Podemos ver el error al intentar llamar a longEar.eat().

Puede que no sea tan obvio, pero si rastreamos longEar.eat() llamada, entonces podemos ver por qué. En ambas líneas (*) y (**) el valor de this es el objeto actual (longEar)., Eso es esencial: todos los métodos de objeto obtienen el objeto actual como this, no un prototipo o algo así.

he Aquí la imagen de lo que sucede:

El problema no puede ser resuelto mediante el uso de this solo.

]

para proporcionar la solución, JavaScript agrega una propiedad interna especial más para las funciones: ].,

Cuando una función se especifica como un método de clase u objeto, su propiedad ] se convierte en ese objeto.

entoncessuper lo usa para resolver el prototipo padre y sus métodos.

veamos cómo funciona, primero con objetos simples:

los métodos no son «libres»

Como hemos sabido antes, generalmente las funciones son «libres», no enlazadas a objetos en JavaScript. Así que pueden ser copiados entre objetos y llamados con otro this.,

la existencia misma de ] viola ese principio, porque los métodos recuerdan sus objetos. ] no se puede cambiar, por lo que este enlace es para siempre.

el único lugar en El lenguaje donde ] se super. Por lo tanto, si un método no utiliza super, entonces todavía podemos considerarlo libre y copiar entre objetos. Pero con super las cosas pueden salir mal.,

Aquí está el demo de un mal super resultado después de copiar:

Una llamada a tree.sayHi() muestra «soy un animal». Definitivamente equivocado.

La razón es simple:

Aquí está el diagrama de lo que sucede:

Métodos, no de propiedades de función

La diferencia puede no ser esencial para nosotros, pero es importante para JavaScript.

en el siguiente ejemplo se usa una sintaxis que no es de método para la comparación., ] la propiedad no está establecida y la herencia no funciona:

resumen

  1. Para extender una clase: class Child extends Parent:
    • eso significa Child.prototype.__proto__ será Parent.prototype, por lo que los métodos se heredan.
  2. Cuando se reemplaza un constructor:
    • debemos llamar a los padres constructor super() en el Child constructor antes de usar this.,
  3. Cuando reemplazar a otro método:
    • podemos usar super.method() en un Child método para llamar a Parent método.
  4. Internals:
    • Methods remember their class / object in the internal ] property. Así es como super resuelve los métodos padre.
    • así que no es Seguro copiar un método con super de un objeto a otro.,

también:

  • Las funciones de flecha no tienen sus propias thiso super, por lo que encajan de forma transparente en el contexto circundante.