Seleccionar página

Cuando es invocado un constructor hay disponible un objeto new que tiene una única propiedad llamada target. En esta propiedad contiene una referencia la clase que se está instanciando. Esto puede parecer un poco extraño, pero es de bastante utilidad para varios casos. Vamos a repasar algunos de ellos:

clases abstractas

En principio en Javascript no tenemos una forma explícita para indicar que una clase es abstracta, es decir, que no se pueden crear objetos directamente de esa clase y sólo se puede heredar de ella. Es muy sencillo implementar este comportamiento comprobando con new.target cual es la clase que se está intentando construir.

class function
class C {
  constructor () {
    if (new.target === C) {
      throw new Error( 'this is an abstract class' );
    }
  }
}
class CC extends C {
}

try {
  const c = new C();
  console.assert( false );
} catch (err) {
  console.assert(
    err.message === 'this is an abstract class'
  );
}
const cc = new CC();
console.assert( cc instanceof C );
function C () {
  if (new.target === C) {
    throw new Error( 'this is an abstract class' );
  }
}
function CC () {
}
CC.prototype = Object.create(
  C.prototype,
  {constructor : {value : C}}
);

try {
  const f = new C();
  console.assert( false );
} catch (err) {
  console.assert(
    err.message === 'this is an abstract class'
  );
}
const cc = new CC();
console.assert( cc instanceof C );

En ese ejemplo impedimos que se puedan crear clases del tipo C, si se intenta se lanza un error. Lo que sí permitimos es que se puedan crear objetos de una clase hija CC.

impedir la herencia

Por medio de new.target podemos detectar que se está instanciando un objeto de una clase diferente a la nuestra, normalmente por algún proceso de herencia, o por medio de una llamada a Reflect.constructor() que combina el constructor de una clase con el prototipo de otra. Confirmando que target.new es de nuestra clase y no de otra, podemos asegurarnos que no se está heredando de nuestra clase.

class function
class C {
  constructor () {
    if (new.target !== C) {
      throw new TypeError( 'Illegal constructor' );
    }
  }
}

class CC extends C {
}

try {
  const cc = new CC();
  console.assert( false );
} catch (err) {
  console.assert( err.message === 'Illegal constructor' );
}
const c = new C();
function C () {
  if (new.target !== C) {
    throw new TypeError( 'Illegal constructor' );
  }
}

function CC () {
  return Reflect.construct(
    Object.getPrototypeOf( Object.getPrototypeOf( this ) ).constructor,
    arguments,
    Object.getPrototypeOf( this ).constructor
  );
}
CC.prototype = Object.create(
  C.prototype,
  {constructor : {value : CC}}
);
Object.setPrototypeOf( CC, C );

try {
  const cc = new CC();
  console.assert( false );
} catch (err) {
  console.assert( err.message === 'Illegal constructor' );
}
const c = new C();

Aunque a primera vista pueda parecer que target.new tiene una utilidad limitada, lo cierto es que es un recursos sencillo para controlar a que se está instanciando con new y poder actuar con diferentes objetivos como los que hemos visto aquí, crear clases abstractas, crear clases no heredables y, en general, controlar cualquier operación con el constructor (se podría controlar también la llamada a un constructor como función).

super Índice