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 |