Seleccionar página

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.

classfunction
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.

    classfunction
    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.

      classfunction
      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 Índicenew.target