llamar al constructor de la clase padre
Cuando se hereda de una clase es muy importante ejecutar correctamente el constructor de la clase padre. De esta forma el constructor de la clase padre se ejecuta y puede comprobar lo que necesita así como configurar el objeto añadiendo las propiedades que requiera.
Con class
disponemos de super()
que llama al constructor de la clase padre. Al llamar a super()
le debemos pasar los parámetros que espera el constructor de la clase padre. Una vez llamado, this
tiene las modificaciones que el constructor haya realizado, por ello se recomiendo llamar a super()
lo antes posible en el código del constructor.
En function
no disponemos de una forma directa para llamar al constructor del padre y la cosa se complica un poco. Existen varias estrategias para hacer esta llamada, pero la más efectiva es hacer uso de Reflect.construct()
pasando como parámetros el constructor de la clase padre, los parámetros que tengamos que pasar al constructor padre y el constructor de la clase hija.
class | function |
class C { constructor ( x ) { this.x = x; } } class CC extends C { constructor ( x, y ) { super( x ); this.y = y; } method () { return this.x + this.y; } } const cc = new CC( 10, 20 ); console.assert( cc.x === 10 ); console.assert( cc.y === 20 ); console.assert( cc.method() === 30 ); | function C ( x ) { this.x = x; } function CC ( x, y ) { const obj = Reflect.construct( Object .getPrototypeOf( Object.getPrototypeOf( this ) ) .constructor, arguments, Object.getPrototypeOf( this ).constructor ); obj.y = y; return obj; } CC.prototype = Object.create( C.prototype, {constructor : {value : CC}} ); Object.setPrototypeOf( CC, C ); CC.prototype.method = function () { return this.x + this.y }; const cc = new CC( 10, 20 ); console.assert( cc.x === 10 ); console.assert( cc.y === 20 ); console.assert( cc.method() === 30 ); |
Si observamos los miembros en cada uno de los niveles de la cadena de prototipos podemos ver que el objeto tiene dos propiedades propias, es decir, definidas en this
. Estas dos propiedades las han creado en la ejecución de los constructores de las clases C
y CC
.
Si usando class
nos olvidamos de llamar a super()
y tenemos un constructor, se lanzará un error con el mensaje Must call super constructor in derived class before accessing 'this' or returning from derived constructor
. Si no tenemos un constructor y estamos heredando de otra clase, se crea uno por defecto que hace la llamada a super()
por nosotros pasándole todos los parámetros.
Con function
no tenemos una solución tan sencilla y no se nos avisará si nos olvidamos de llamar al constructor de la clase padre. Si, además no estamos utilizando ES6 no tenemos disponible Reflec.construc()
y tenemos que utilizar alguna otra estrategia para invocar al constructor de la clase padre, desde llamarlo con una recontextualización del this
del constructor, algo como: Object.getPrototypeOf( this ).constructor.apply( this, args )
o simplemente llamando al constructor con new
y modificando el prototipo del objeto que devuelva. En definitiva, bastante engorroso.
llamar a métodos de la clase padre
Cuando una clase hija sobrescribe un método de la clase padre, ese método original queda inaccesible en el objeto, queda oculto en la cadena de prototipos. Con super
tenemos una solución para poder acceder al padre y llamar los métodos originales. En el caso de function
tenemos que obtener el prototipo de la clase padre para hacer la llamada con una recontextualización del this
.
class | function |
class C { method ( x ) { return x * 2; } } class CC extends C { method ( x ) { return (super.method( x )).toString(); } } const cc = new CC(); console.assert( cc.method( 2 ) === '4' ); | function C () { } C.prototype.method = function ( x ) { return x * 2; }; function CC () { } CC.prototype = Object.create( C.prototype, {constructor : {value : CC}} ); Object.setPrototypeOf(CC, C); CC.prototype.method = function ( x ) { const father = Object.getPrototypeOf( Object.getPrototypeOf( (this) ) ); return (father.method.call( this, x )).toString(); }; const cc = new CC(); console.assert( cc.method( 2 ) === '4' ); |
Aunque en este ejemplo hemos llamado al método con igual nombre que el padre, en la práctica podremos llamar a cualquier método del padre que haya quedado oculto en la herencia.
acceder a propiedades de la clase padre
Aunque normalmente las propiedades se escriben en el propio objeto, es perfectamente viable escribir propiedades en los prototipos. En ese caso es posible acceder a la propiedad del prototipo aunque se haya sobrescrito.
class | function |
class C { } C.prototype.x = 1; class CC extends C { origin () { return super.x; } } const cc = new CC(); cc.x = 2; console.assert( cc.x === 2 ); console.assert( cc.origin() === 1 ); | function C ( x ) { } C.prototype.x = 1; function CC ( x, y ) { } CC.prototype = Object.create( C.prototype, {constructor : {value : CC}} ); Object.setPrototypeOf( CC, C ); CC.prototype.origin = function () { return ( Object.getPrototypeOf( Object.getPrototypeOf( this ) ).x ); }; const cc = new CC(); cc.x = 2; console.assert( cc.x === 2 ); console.assert( cc.origin() === 1 ); |
El uso de super
, y sus equivalentes más o menos sencillos en function
, nos ofrecen una gran flexibilidad a la hora de acceder e invocar a miembros de las clases de las que heredamos, evitando los problemas de sobrescritura de propiedades y métodos, y permitiendo en todo momento tener a nuestro alcance todos los elementos que necesitemos.
herencia | Índice | new.target |