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.,
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 Rabbit
tiene el método stop
que 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 usarthis
.
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 athis
. - 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
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,]
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
- Para extender una clase:
class Child extends Parent
:- eso significa
Child.prototype.__proto__
seráParent.prototype
, por lo que los métodos se heredan.
- eso significa
- Cuando se reemplaza un constructor:
- debemos llamar a los padres constructor
super()
en elChild
constructor antes de usarthis
.,
- debemos llamar a los padres constructor
- Cuando reemplazar a otro método:
- podemos usar
super.method()
en unChild
método para llamar aParent
método.
- podemos usar
- Internals:
- Methods remember their class / object in the internal
]
property. Así es comosuper
resuelve los métodos padre. - así que no es Seguro copiar un método con
super
de un objeto a otro.,
- Methods remember their class / object in the internal
también:
- Las funciones de flecha no tienen sus propias
this
osuper
, por lo que encajan de forma transparente en el contexto circundante.
Deja una respuesta