Pregunta 1Después de class Coupon {}, ¿qué pasa si llamas a Coupon("SPRING") sin new?
Fundamentos de class — definir una clase y crear instancias
Cómo usar class para crear muchos objetos con la misma forma: instancias con constructor y new, métodos que leen this y cuándo basta un objeto literal.
Socios, productos, cupones: los datos que tienen los mismos campos y se usan en los mismos cálculos aparecen una y otra vez en una aplicación. Si escribes cada uno como un objeto literal, terminas copiando los campos y la fórmula en cada elemento.
En este artículo verás class, que reúne en un solo lugar la forma de los datos y el código que trabaja con ellos, y cómo crear objetos a partir de ella, uno por uno, con new.
Definir la forma de los datos en un solo lugar — class y new
Imagina que manejas dos tipos de cupones de descuento. Si escribes { code: "WELCOME", discount: 500 } una vez por cupón y en uno solo te equivocas y pones discont, no se lanza ningún error: simplemente el descuento de ese cupón queda en undefined. Como los nombres de las claves se repiten en cada cupón, te toca revisarlos todos para encontrar la errata.
Una clase (la sintaxis class, que define un tipo de objeto que se crea con new) se escribe como class Coupon { ... }, y dentro va un constructor (la parte a la que llama new), sin la palabra clave function.
Dentro del constructor, this es la nueva instancia (un objeto creado a partir de una clase), y new la devuelve aunque no haya return.
// Define en un solo lugar la forma de un cupón con una clase
class Coupon {
constructor(code, discount) {
this.code = code; // Izquierda: el code de la instancia. Derecha: el argumento recibido
this.discount = discount;
}
}
// Crea los cupones uno por uno con new
const welcome = new Coupon("WELCOME", 500);
const summer = new Coupon("SUMMER", 1000);
console.log(welcome.code); // WELCOME
console.log(summer.discount); // 1000
console.log(welcome instanceof Coupon); // true
// Si la llamas sin new, se lanza un error
// Coupon("SPRING", 300); // TypeError
this dentro del constructor. Sin new, la llamada lanza un error antes de entrar al constructor.Los nombres de las claves aparecen en un solo lugar, dentro del constructor, así que una errata como discont solo puede estar ahí y solo hay un lugar que corregir. En el caso de la columna derecha del diagrama, la consola muestra TypeError: Class constructor Coupon cannot be invoked without 'new'.
Calcular con los valores de una instancia — métodos y this
Si calculas el importe con descuento con total - welcome.discount tanto en la pantalla del carrito como en la de confirmación del pedido, cada vez que cambie la regla de precios tendrás dos lugares que corregir. Aunque lo muevas a una función, los campos siguen en la clase y el cálculo queda fuera de ella.
Un método (una función que pertenece a un objeto y se llama con un punto, como welcome.apply(3000)) se escribe dentro de las llaves de la clase como apply(total) { ... }, también sin function. Dentro de un método, this es la instancia escrita a la izquierda del punto, y this.discount lee su valor en el momento de la llamada.
class Coupon {
constructor(code, discount) {
this.code = code;
this.discount = discount;
}
// Resta del total el descuento de este cupón y devuelve el resultado
apply(total) {
return total - this.discount; // this es la instancia a la izquierda del punto
}
}
// Crea dos tipos de cupones
const welcome = new Coupon("WELCOME", 500);
const summer = new Coupon("SUMMER", 1000);
// El mismo apply da otro resultado según la instancia a la izquierda del punto
console.log(welcome.apply(3000)); // 2500
console.log(summer.apply(3000)); // 2000
this es la variable escrita a la izquierda de apply. Con el mismo argumento, un this distinto da un resultado distinto.- Argumentos
code/discount: solo se usan dentro de estas llaves this.discount = discount: guarda el valor en la instancia
this.discount: lee el valor guardado en la instanciatotal - discount: el nombre no es visible,ReferenceError
discount no es visible fuera de las llaves del constructor. apply lee, en cambio, el valor guardado en this.Si olvidas this. dentro de apply y escribes total - discount, JavaScript busca el nombre discount empezando por las llaves más internas y avanzando hacia afuera. Por eso el resultado depende de si existe fuera de la clase una variable con el mismo nombre.
Olvidar this. no siempre lanza un error
Si fuera de la clase hay una variable con el mismo nombre, como const discount = 300;, el total - discount sin this. no lanza un ReferenceError: usa ese 300 sin avisar. Si todos los cupones devuelven el mismo importe sin importar con cuál llames al método, sospecha que falta un this..
Cada instancia guarda sus propios valores — varias instancias
Cada vez que se usa un cupón, la cantidad de usos que le quedan baja en uno. Si guardas esas cantidades fuera de la clase, en un objeto como { WELCOME: 3, SUMMER: 3 }, cada pantalla que use cupones necesita su propio código para buscar la cantidad por code y volver a escribir el nuevo valor.
Dentro de un método también puedes asignar a una propiedad de this, como en this.remaining = this.remaining - 1. El lado derecho lee la cantidad actual y el resultado, uno menos, se escribe de nuevo en el remaining de la misma instancia. Así, la cantidad empieza en el 3 que pasaste a new y baja en uno con cada llamada.
class Coupon {
constructor(code, remaining) {
this.code = code;
this.remaining = remaining; // Cuántos usos quedan
}
// Resta uno a la cantidad de la instancia a la izquierda del punto
use() {
this.remaining = this.remaining - 1;
}
}
// Crea dos cupones con la misma cantidad y úsalos un número distinto de veces
const welcome = new Coupon("WELCOME", 3);
const summer = new Coupon("SUMMER", 3);
welcome.use();
welcome.use();
summer.use();
console.log(welcome.remaining); // 1
console.log(summer.remaining); // 2
Las instancias se pueden guardar tanto en variables como en los elementos de un array. Si llamas a un método sobre un elemento, como en coupons[0].use(), this es ese elemento. Tengas los cupones que tengas, el código que baja la cantidad es la única línea que hay dentro de use().
Elegir según cuántos necesitas — class frente a objeto literal
Para datos que usas una sola vez, como la configuración de una pantalla, basta con escribir { ... } en el momento. Pero con datos como los cupones, donde creas muchos elementos con la misma forma y el mismo cálculo, escribir literales te obliga a copiar hasta el cuerpo del cálculo en cada uno.
Un objeto literal también puede tener métodos: escribe apply(total) { ... } junto a los pares clave-valor, y el this de adentro es ese objeto. Lo que cambia respecto a una clase es dónde se escribe el cuerpo del método, y esa diferencia aparece en el resultado de Object.keys.
// class: el cuerpo se escribe una sola vez, dentro de la definición
class Coupon {
constructor(code, discount) {
this.code = code;
this.discount = discount;
}
apply(total) { return total - this.discount; }
}
const welcome = new Coupon("WELCOME", 500);
// Literal: el cuerpo se escribe completo en cada elemento
const summer = {
code: "SUMMER",
discount: 1000,
apply(total) { return total - this.discount; },
};
// Tienen claves distintas
console.log(Object.keys(welcome).join(", ")); // code, discount
console.log(Object.keys(summer).join(", ")); // code, discount, apply
El cuerpo está en un lugar distinto, pero welcome.apply(3000) se llama exactamente igual que summer.apply(3000), así que el código que llama no cambia. Cómo puede una instancia llamar a un método cuyo cuerpo está en la clase lo verás en el artículo sobre prototipos. La tabla de abajo compara las dos formas.
| Aspecto | Objeto literal | class |
|---|---|---|
| Objetos creados | Solo el { } que escribes | Uno por cada new |
| Cuerpo del método | Copiado en cada uno | Una vez en la clase |
| Claves en Object.keys | code, discount, apply | code, discount |
| instanceof Coupon | false | true |
| Ideal para | Ajustes o datos de un solo uso | Muchos con la misma lógica |
Sin comas después de los métodos de una clase
Al convertir un literal en una clase, si dejas la coma después de un método, como en apply(total) { ... },, obtienes SyntaxError: Unexpected token ',' y no se ejecuta ni una línea. Dentro de las llaves de una clase, los métodos van uno tras otro, sin separador.
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2Fuera de la clase hay una variable discount que vale 300. Si falta this., ¿qué devuelve welcome.apply(3000)?
Pregunta 3¿Qué devuelve Object.keys para una instancia de una clase con constructor(code, discount) y apply?