constructores - ¿son las clases es6 solo azúcar sintáctico para el patrón prototípico en javascript?
javascript parent class (6)
¿Las nuevas clases de ES6 son simplemente azúcar sintáctica para el antiguo patrón prototípico?
Sí, son (casi por completo) una sintaxis de conveniencia, la semántica es casi idéntica. La respuesta de Traktor53 entra en las diferencias.
El siguiente ejemplo de código corto muestra cómo se establecen las funciones en una
class
en el objeto
prototype
.
class Thing {
someFunc() {}
}
console.log("someFunc" in Thing.prototype); // true
Después de jugar con ES6, realmente empecé a gustarme la nueva sintaxis y las funciones disponibles, pero tengo una pregunta sobre las clases.
¿Las nuevas clases ES6 son simplemente azúcar sintáctica para el antiguo patrón prototípico? o hay más pasando aquí detrás de escena? es decir:
class Thing {
//... classy stuff
doStuff(){}
}
vs:
var Thing = function() {
// ... setup stuff
};
Thing.prototype.doStuff = function() {}; // etc
Sí, tal vez, pero parte del azúcar sintáctico tiene dientes.
La declaración de una clase crea un objeto de función que es el constructor de la clase, utilizando el código proporcionado para el
constructor
dentro del cuerpo de la clase, y para las clases con nombre, con el mismo nombre que la clase.
La función de constructor de clase tiene un objeto prototipo normal del que las instancias de clase heredan propiedades en forma JavaScript normal. Los métodos de instancia definidos dentro del cuerpo de la clase se agregan a este prototipo.
ES6 no proporciona un medio para declarar valores de propiedad predeterminados de instancia de clase (es decir, valores que no son métodos) dentro del cuerpo de la clase para ser almacenados en el prototipo y heredados.
Para inicializar el valor de instancia, puede establecerlos como propiedades locales, no heredadas dentro del constructor, o agregarlos manualmente al objeto
prototype
del constructor de clase fuera de la definición de clase de la misma manera que para las funciones de constructor ordinarias.
(No estoy discutiendo los méritos o de lo contrario de configurar propiedades heredadas para las clases de JavaScript).
Los métodos estáticos declarados dentro del cuerpo de la clase se agregan como propiedades de la función constructora de la clase.
Evite el uso de nombres de métodos de clase estáticos que compitan con las propiedades de funciones estándar y los métodos heredados de
Function.prototype
como
call
,
apply
o
length
.
Menos azucarado es que las declaraciones y métodos de clase siempre se ejecutan en modo estricto, y una característica que recibe poca atención: la propiedad
.prototype
de las funciones del constructor de clases es de solo lectura: no puede establecerla en algún otro objeto que haya creado para Algún propósito especial.
Algunas cosas interesantes suceden cuando extiendes una clase:
-
La propiedad del objeto
prototype
constructor de la clase extendida se prototipa automáticamente en el objetoprototype
de la clase que se está extendiendo. Esto no es particularmente nuevo y el efecto puede duplicarse usandoObject.create
. -
la función de constructor de clase extendida (objeto) se crea automáticamente un prototipo en la función de constructor de la clase que se está extendiendo, no en
Function
. Si bien es posible replicar el efecto en una función de constructor ordinario usandoObject.setPrototypeOf
o inclusochildClass.__proto__ = parentClass
, esta sería una práctica de codificación extremadamente inusual y a menudo se desaconseja en la documentación de JavaScript.
Existen otras diferencias, como los objetos de clase que no se izan en la forma de funciones nombradas declaradas utilizando la palabra clave de
function
.
Creo que podría ser ingenuo pensar que las declaraciones y expresiones de clase permanecerán inalteradas en todas las versiones futuras de ECMA Script y será interesante ver si ocurren desarrollos y cuándo. Podría decirse que se ha convertido en una moda asociar el "azúcar sintáctico" con las clases introducidas en ES6 (ECMA-262 versión estándar 6), pero personalmente trato de evitar repetirlo.
Si casi.
Con es6 puede extender la clase Function y la clase Array, en es5 no puede tener el mismo comportamiento: extender Function no crea un objeto invocable y extender Array no hereda la propiedad automática .length en es5
Para el resto, la lógica del prototipo y las clases son las mismas en JavaScript
Si. Pero son más estrictos.
Hay dos diferencias principales en sus ejemplos.
En primer lugar, con la sintaxis de clase, no puede inicializar una instancia sin una
new
palabra clave.
class Thing{}
Thing() //Uncaught TypeError: Class constructor Thing cannot be invoked without ''new''
var Thing = function() {
if(!(this instanceof Thing)){
return new Thing();
}
};
Thing(); //works
La segunda es que las clases definidas con sintaxis de clase tienen un alcance de bloque.
Es similar a definir variables con la palabra clave
let
.
class Thing{}
class Thing{} //Uncaught SyntaxError: Identifier ''Thing'' has already been declared
{
class Thing{}
}
console.log(Thing); //Uncaught ReferenceError: Thing is not defined
Editar
Como @zeroflagL mencionó en su comentario, las declaraciones de clase tampoco se izan.
console.log(Thing) //Uncaught ReferenceError: Thing is not defined
class Thing{}
Son azúcares totalmente sintácticos.
Lo nuevo de la herencia prototípica en ES6 es la redefinición de la propiedad
__proto__
de los objetos.
__proto__
es legal ahora y así es como se ha hecho posible la subclase de matrices con JS.
No, las clases ES6 no son solo azúcar sintáctico para el patrón prototípico.
Si bien lo contrario se puede leer en muchos lugares y, aunque parece ser cierto en la superficie, las cosas se vuelven más complejas cuando comienzas a profundizar en los detalles.
No estaba muy satisfecho con las respuestas existentes. Después de investigar un poco más, así es como clasifiqué las características de las clases de ES6 en mi mente:
- Azúcar sintáctico para el patrón de herencia pseudoclásica estándar ES5.
- Azúcar sintáctico para mejorar el patrón de herencia pseudoclásico disponible pero poco práctico o poco común en ES5.
- Azúcar sintáctico para mejoras en el patrón de herencia pseudoclásico no disponible en ES5, pero que puede implementarse en ES6 sin la sintaxis de clase.
-
Características imposibles de implementar sin la sintaxis de
class
, incluso en ES6.
(He intentado que esta respuesta sea lo más completa posible y, como resultado, se hizo bastante larga. Aquellos más interesados en una buena visión general deberían ver la respuesta de traktor53 ).
Permítanme ''desugar'' paso a paso (y en la medida de lo posible) las declaraciones de clase a continuación para ilustrar las cosas a medida que avanzamos:
// Class Declaration:
class Vertebrate {
constructor( name ) {
this.name = name;
this.hasVertebrae = true;
this.isWalking = false;
}
walk() {
this.isWalking = true;
return this;
}
static isVertebrate( animal ) {
return animal.hasVertebrae;
}
}
// Derived Class Declaration:
class Bird extends Vertebrate {
constructor( name ) {
super( name )
this.hasWings = true;
}
walk() {
console.log( "Advancing on 2 legs..." );
return super.walk();
}
static isBird( animal ) {
return super.isVertebrate( animal ) && animal.hasWings;
}
}
1. Azúcar sintáctico para el patrón de herencia pseudoclásica estándar ES5
En esencia, las clases de ES6 de hecho proporcionan azúcar sintáctica para el patrón de herencia pseudoclásica estándar de ES5.
Declaraciones de clase / expresiones
En el fondo, una declaración de clase o una expresión de clase creará una función de constructor con el mismo nombre que la clase de manera que:
-
La propiedad interna
[[Construct]]
del constructor se refiere al bloque de código adjunto al método de la clase ''constructor()
. -
Los métodos de clase se definen en la propiedad
prototype
del constructor (no estamos incluyendo métodos estáticos por ahora).
Usando la sintaxis de ES5, la declaración de clase inicial es, por lo tanto, más o menos equivalente a la siguiente (dejando de lado los métodos estáticos):
function Vertebrate( name ) { // 1. A constructor function containing the code of the class''s constructor method is defined
this.name = name;
this.hasVertebrae = true;
this.isWalking = false;
}
Object.assign( Vertebrate.prototype, { // 2. Class methods are defined on the constructor''s prototype property
walk: function() {
this.isWalking = true;
return this;
}
} );
La declaración de clase inicial y el fragmento de código anterior producirán lo siguiente:
console.log( typeof Vertebrate ) // function
console.log( typeof Vertebrate.prototype ) // object
console.log( Object.getOwnPropertyNames( Vertebrate.prototype ) ) // [ ''constructor'', ''walk'' ]
console.log( Vertebrate.prototype.constructor === Vertebrate ) // true
console.log( Vertebrate.prototype.walk ) // [Function: walk]
console.log( new Vertebrate( ''Bob'' ) ) // Vertebrate { name: ''Bob'', hasVertebrae: true, isWalking: false }
Declaraciones / expresiones de clase derivadas
Además de lo anterior, las declaraciones de clase derivadas o las expresiones de clase derivadas también establecerán una herencia entre las propiedades
prototype
los constructores y harán uso de la
super
sintaxis de manera que:
-
La propiedad
prototype
del constructor hijo hereda de la propiedadprototype
del constructor padre. -
La llamada
super()
equivale a llamar al constructor padre conthis
vinculado al contexto actual.-
Esta es solo una aproximación aproximada de la funcionalidad proporcionada por
super()
, que también establecería el parámetro implícitonew.target
ynew.target
método interno[[Construct]]
(en lugar del método[[Call]]
). La llamadasuper()
se "desugared" completamente en la sección 3 .
-
Esta es solo una aproximación aproximada de la funcionalidad proporcionada por
-
Las llamadas
super[method]()
equivalen a llamar al método en el objetoprototype
del padre conthis
vinculado al contexto actual (no estamos incluyendo métodos estáticos por ahora).-
Esto es solo una aproximación de
super[method]()
llamadassuper[method]()
que no se basan en una referencia directa a una clase principal.super[method]()
llamadassuper[method]()
se replicarán completamente en la sección 3 .
-
Esto es solo una aproximación de
Usando la sintaxis de ES5, la declaración de clase derivada inicial es, por lo tanto, más o menos equivalente a lo siguiente (dejando de lado los métodos estáticos):
function Bird( name ) {
Vertebrate.call( this, name ) // 2. The super() call is approximated by directly calling the parent constructor
this.hasWings = true;
}
Bird.prototype = Object.create( Vertebrate.prototype, { // 1. Inheritance is established between the constructors'' prototype properties
constructor: {
value: Bird,
writable: true,
configurable: true
}
} );
Object.assign( Bird.prototype, {
walk: function() {
console.log( "Advancing on 2 legs..." );
return Vertebrate.prototype.walk.call( this ); // 3. The super[method]() call is approximated by directly calling the method on the parent''s prototype object
}
})
La declaración de clase derivada inicial y el fragmento de código anterior producirán lo siguiente:
console.log( Object.getPrototypeOf( Bird.prototype ) ) // Vertebrate {}
console.log( new Bird("Titi") ) // Bird { name: ''Titi'', hasVertebrae: true, isWalking: false, hasWings: true }
console.log( new Bird( "Titi" ).walk().isWalking ) // true
2. Azúcar sintáctico para mejorar el patrón de herencia pseudoclásico disponible pero poco práctico o poco común en ES5
Las clases de ES6 proporcionan además mejoras al patrón de herencia pseudoclásico que ya podría haberse implementado en ES5, pero a menudo se omitieron, ya que podría ser un poco poco práctico configurarlo.
Declaraciones de clase / expresiones
Una declaración de clase o una expresión de clase configurará las cosas de la siguiente manera:
- Todo el código dentro de la declaración de clase o expresión de clase se ejecuta en modo estricto.
- Los métodos estáticos de la clase se definen en el propio constructor.
- Todos los métodos de clase (estáticos o no) no son enumerables.
- La propiedad prototipo del constructor no se puede escribir.
Usando la sintaxis de ES5, la declaración de clase inicial es, por lo tanto, más precisamente (pero solo parcialmente) equivalente a lo siguiente:
var Vertebrate = (function() { // 1. Code is wrapped in an IIFE that runs in strict mode
''use strict'';
function Vertebrate( name ) {
this.name = name;
this.hasVertebrae = true;
this.isWalking = false;
}
Object.defineProperty( Vertebrate.prototype, ''walk'', { // 3. Methods are defined to be non-enumerable
value: function walk() {
this.isWalking = true;
return this;
},
writable: true,
configurable: true
} );
Object.defineProperty( Vertebrate, ''isVertebrate'', { // 2. Static methods are defined on the constructor itself
value: function isVertebrate( animal ) { // 3. Methods are defined to be non-enumerable
return animal.hasVertebrae;
},
writable: true,
configurable: true
} );
Object.defineProperty( Vertebrate, "prototype", { // 4. The constructor''s prototype property is defined to be non-writable:
writable: false
});
return Vertebrate
})();
-
NB 1 : Si el código circundante ya se está ejecutando en modo estricto, por supuesto no hay necesidad de envolver todo en un IIFE.
-
Nota 2 : aunque fue posible definir propiedades estáticas sin problemas en ES5, esto no era muy común. La razón de esto puede ser que establecer la herencia de propiedades estáticas no era posible sin el uso de la propiedad
__proto__
no estándar.
Ahora, la declaración de clase inicial y el fragmento de código anterior también producirán lo siguiente:
console.log( Object.getOwnPropertyDescriptor( Vertebrate.prototype, ''walk'' ) )
// { value: [Function: walk],
// writable: true,
// enumerable: false,
// configurable: true }
console.log( Object.getOwnPropertyDescriptor( Vertebrate, ''isVertebrate'' ) )
// { value: [Function: isVertebrate],
// writable: true,
// enumerable: false,
// configurable: true }
console.log( Object.getOwnPropertyDescriptor( Vertebrate, ''prototype'' ) )
// { value: Vertebrate {},
// writable: false,
// enumerable: false,
// configurable: false }
Declaraciones / expresiones de clase derivadas
Además de lo anterior, las declaraciones de clase derivadas o las expresiones de clase derivadas también harán uso de la
super
sintaxis de manera que:
-
Las llamadas
super[method]()
dentro de los métodos estáticos equivalen a llamar al método en el constructor del padre conthis
vinculado al contexto actual.-
Esto es solo una aproximación de
super[method]()
llamadassuper[method]()
que no se basan en una referencia directa a una clase principal.super[method]()
llamadassuper[method]()
en métodos estáticos no pueden imitarse completamente sin el uso de la sintaxis declass
y se enumeran en la sección 4.
-
Esto es solo una aproximación de
Usando la sintaxis ES5, la declaración de clase derivada inicial es, por lo tanto, más precisamente (pero solo parcialmente) equivalente a lo siguiente:
function Bird( name ) {
Vertebrate.call( this, name )
this.hasWings = true;
}
Bird.prototype = Object.create( Vertebrate.prototype, {
constructor: {
value: Bird,
writable: true,
configurable: true
}
} );
Object.defineProperty( Bird.prototype, ''walk'', {
value: function walk( animal ) {
return Vertebrate.prototype.walk.call( this );
},
writable: true,
configurable: true
} );
Object.defineProperty( Bird, ''isBird'', {
value: function isBird( animal ) {
return Vertebrate.isVertebrate.call( this, animal ) && animal.hasWings; // 1. The super[method]() call is approximated by directly calling the method on the parent''s constructor
},
writable: true,
configurable: true
} );
Object.defineProperty( Bird, "prototype", {
writable: false
});
Ahora, la declaración de clase derivada inicial y el fragmento de código anterior también producirán lo siguiente:
console.log( Bird.isBird( new Bird("Titi") ) ) // true
3. Azúcar sintáctico para mejorar el patrón de herencia pseudoclásico no disponible en ES5
Las clases de ES6 proporcionan además mejoras al patrón de herencia pseudoclásico que no están disponibles en ES5, pero se pueden implementar en ES6 sin tener que usar la sintaxis de clase.
Declaraciones de clase / expresiones
Las características de ES6 que se encuentran en otros lugares también se convirtieron en clases, en particular:
-
Las declaraciones de clase se comportan como declaraciones de
let
: no se inicializan cuando se izan y terminan en la Zona muerta temporal antes de la declaración. ( question relacionada) -
El nombre de la clase se comporta como un enlace
const
dentro de la declaración de la clase; no se puede sobrescribir dentro de un método de la clase; si intenta hacerlo, se producirá unTypeError
. -
Los constructores de clase se deben invocar con el método interno
[[Construct]]
, se lanza unTypeError
si se invocan como funciones ordinarias con el método interno[[Call]]
. -
Los métodos de clase (con la excepción del método
constructor()
), estáticos o no, se comportan como los métodos definidos a través de la sintaxis del método conciso, lo que significa que:-
Pueden usar la palabra clave
super
través desuper.prop
osuper[method]
(esto se debe a que se les asigna una propiedad interna[[HomeObject]]
). -
No se pueden usar como constructores; carecen de una propiedad
prototype
y una propiedad interna[[Construct]]
.
-
Pueden usar la palabra clave
Con la sintaxis de ES6, la declaración de clase inicial es, por lo tanto, aún más precisa (pero solo parcialmente) equivalente a lo siguiente:
let Vertebrate = (function() { // 1. The constructor is defined with a let declaration, it is thus not initialized when hoisted and ends up in the TDZ
''use strict'';
const Vertebrate = function( name ) { // 2. Inside the IIFE, the constructor is defined with a const declaration, thus preventing an overwrite of the class name
if( typeof new.target === ''undefined'' ) { // 3. A TypeError is thrown if the constructor is invoked as an ordinary function without new.target being set
throw new TypeError( `Class constructor ${Vertebrate.name} cannot be invoked without ''new''` );
}
this.name = name;
this.hasVertebrae = true;
this.isWalking = false;
}
Object.assign( Vertebrate, {
isVertebrate( animal ) { // 4. Methods are defined using the concise method syntax
return animal.hasVertebrae;
},
} );
Object.defineProperty( Vertebrate, ''isVertebrate'', {enumerable: false} );
Vertebrate.prototype = {
constructor: Vertebrate,
walk() { // 4. Methods are defined using the concise method syntax
this.isWalking = true;
return this;
},
};
Object.defineProperty( Vertebrate.prototype, ''constructor'', {enumerable: false} );
Object.defineProperty( Vertebrate.prototype, ''walk'', {enumerable: false} );
return Vertebrate;
})();
-
Nota 1 : Aunque los métodos de instancia y estáticos se definen con la sintaxis del método conciso, las
super
no se comportarán como se espera en los métodos estáticos. De hecho, la propiedad interna[[HomeObject]]
no es copiada porObject.assign()
. Establecer la propiedad[[HomeObject]]
correctamente en métodos estáticos requeriría que definamos un constructor de funciones usando un objeto literal, lo cual no es posible. -
NB 2 : para evitar que los constructores se invoquen sin la
new
palabra clave, se podrían implementar salvaguardas similares en ES5 haciendo uso del operadorinstanceof
. Sin embargo, esos no cubrían todos los casos (ver esta answer ).
Ahora, la declaración de clase inicial y el fragmento de código anterior también producirán lo siguiente:
Vertebrate( "Bob" ); // TypeError: Class constructor Vertebrate cannot be invoked without ''new''
console.log( Vertebrate.prototype.walk.hasOwnProperty( ''prototype'' ) ) // false
new Vertebrate.prototype.walk() // TypeError: Vertebrate.prototype.walk is not a constructor
console.log( Vertebrate.isVertebrate.hasOwnProperty( ''prototype'' ) ) // false
new Vertebrate.isVertebrate() // TypeError: Vertebrate.isVertebrate is not a constructor
Declaraciones / expresiones de clase derivadas
Además de lo anterior, lo siguiente también se aplicará a una declaración de clase derivada o expresión de clase derivada:
- El constructor hijo hereda del constructor padre (es decir, las clases derivadas heredan miembros estáticos).
-
Llamar a
super()
en el constructor de la clase derivada equivale a llamar al método interno[[Construct]]
del constructor padre con el valor actualnew.target
y vincularthis
contexto al objeto devuelto.
Usando la sintaxis ES6, la declaración de clase derivada inicial es, por lo tanto, más precisamente (pero solo parcialmente) equivalente a lo siguiente:
let Bird = (function() {
''use strict'';
const Bird = function( name ) {
if( typeof new.target === ''undefined'' ) {
throw new TypeError( `Class constructor ${Bird.name} cannot be invoked without ''new''` );
}
const that = Reflect.construct( Vertebrate, [name], new.target ); // 2. super() calls amount to calling the parent constructor''s [[Construct]] method with the current new.target value and binding the ''this'' context to the returned value (see NB 2 below)
that.hasWings = true;
return that;
}
Bird.prototype = {
constructor: Bird,
walk() {
console.log( "Advancing on 2 legs..." );
return super.walk(); // super[method]() calls can now be made using the concise method syntax (see 4. in Class Declarations / Expressions above)
},
};
Object.defineProperty( Bird.prototype, ''constructor'', {enumerable: false} );
Object.defineProperty( Bird.prototype, ''walk'', {enumerable: false} );
Object.assign( Bird, {
isBird: function( animal ) {
return Vertebrate.isVertebrate( animal ) && animal.hasWings; // super[method]() calls can still not be made in static methods (see NB 1 in Class Declarations / Expressions above)
}
})
Object.defineProperty( Bird, ''isBird'', {enumerable: false} );
Object.setPrototypeOf( Bird, Vertebrate ); // 1. Inheritance is established between the constructors directly
Object.setPrototypeOf( Bird.prototype, Vertebrate.prototype );
return Bird;
})();
-
Nota 1 :
Object.create()
solo se puede usar para establecer el prototipo de un nuevo objeto sin función, la configuración de la herencia entre los propios constructores solo se puede implementar en ES5 manipulando la propiedad no estándar__proto__
. -
NB 2 : No es posible imitar el efecto de
super()
usando el contextothis
, por lo que tuvimos que devolver un objeto diferente explícitamente del constructor.
Ahora, la declaración de clase derivada inicial y el fragmento de código anterior también producirán lo siguiente:
console.log( Object.getPrototypeOf( Bird ) ) // [Function: Vertebrate]
console.log( Bird.isVertebrate ) // [Function: isVertebrate]
4. Características imposibles de implementar sin la sintaxis de
class
Las clases de ES6 proporcionan además las siguientes características que no se pueden implementar en absoluto sin usar realmente la sintaxis de
class
:
-
La propiedad interna
[[HomeObject]]
de los métodos de clase estática apunta al constructor de la clase.-
No hay forma de implementar esto para las funciones de constructor ordinarias, ya que requeriría definir una función a través de un objeto literal (ver también la sección 3 anterior).
Esto es particularmente problemático para los métodos estáticos de clases derivadas que utilizan la palabra clave
super
como nuestro métodoBird.isBird()
.
-
No hay forma de implementar esto para las funciones de constructor ordinarias, ya que requeriría definir una función a través de un objeto literal (ver también la sección 3 anterior).
Esto es particularmente problemático para los métodos estáticos de clases derivadas que utilizan la palabra clave
Es posible solucionar parcialmente este problema si la clase principal se conoce de antemano.
Conclusión
Algunas características de las clases de ES6 son simplemente azúcar sintáctica para el patrón de herencia pseudoclásico estándar de ES5. Sin embargo, las clases de ES6 también vienen con características que solo se pueden implementar en ES6 y algunas características adicionales que ni siquiera se pueden imitar en ES6 (es decir, sin usar la sintaxis de clase).
Mirando lo anterior, creo que es justo decir que las clases ES6 son más concisas, más convenientes y más seguras de usar que el patrón de herencia pseudoclásico ES5. Como resultado, también son menos flexibles (vea esta pregunta, por ejemplo).
Notas al margen
Vale la pena señalar algunas peculiaridades más de clases que no encontraron un lugar en la clasificación anterior:
-
super()
solo es una sintaxis válida en constructores de clases derivadas y solo se puede llamar una vez. -
Intentar acceder a
this
en un constructor de clase derivado antes de quesuper()
se llame resultados en unReferenceError
. -
super()
debe llamar asuper()
en un constructor de clase derivado si no se devuelve ningún objeto explícitamente. -
eval
y losarguments
no son identificadores de clase válidos (mientras que son identificadores de función válidos en modo no estricto). -
Las clases derivadas configuran un método de
constructor()
predeterminado si no se proporciona ninguno (correspondiente alconstructor( ...args ) { super( ...args ); }
). - No es posible definir propiedades de datos en una clase con una declaración de clase o una expresión de clase (aunque puede agregar propiedades de datos en la clase manualmente después de su declaración).
Recursos adicionales
- El capítulo Comprensión de las clases de ES6 en Comprensión de ES6 de Nicholas Zakas es el mejor artículo sobre las clases de ES6 que he encontrado.
- El blog 2ality de Axel Rauschmayer tiene una post muy completa sobre las clases de ES6.
- Object Playground tiene un excelente video que explica el patrón de herencia pseudoclásico (y lo compara con la sintaxis de la clase).
- El transpilador de Babel es un buen lugar para explorar cosas por su cuenta.