Pregunta 1Con un getter que devuelve 30 - this.reserved, ¿qué obtienes si cambias reserved a 20 y luego lees el getter?
Getters y setters — ejecutar código al leer o asignar una propiedad
Los getters y setters ejecutan código al leer o asignar una propiedad. Propiedades calculadas, validación de valores asignados, pasar por el setter desde el constructor y propiedades de solo lectura.
Supón que la página de inscripción a un seminario muestra los lugares disponibles como «capacidad − inscripciones». Si calculas los lugares disponibles al crear la instancia y guardas el resultado, lo que se muestra no cambia cuando se inscribe más gente. Y si alguien asigna una capacidad menor que el número de inscripciones, nada lo impide.
Este artículo trata los getters y setters, que ejecutan código cuando se lee o se asigna una propiedad.
Recalcular en cada lectura — get y propiedades calculadas
Si calculas los lugares disponibles como «capacidad − inscripciones» en el constructor y guardas el resultado, se quedan con el valor del momento de la creación aunque después cambies el número de inscripciones. Si lo conviertes en un método, puedes recalcularlo, pero entonces quien lo usa tiene que acordarse de no poner paréntesis para la capacidad y sí ponerlos para los lugares disponibles.
Un getter (un método de una clase con get delante, que se ejecuta cuando se lee la propiedad) se escribe sin parámetros, como get remainingSeats() { ... }, y al leer la propiedad obtienes lo que devuelve con return.
Una propiedad como remainingSeats, que se calcula a partir de otras propiedades, se llama propiedad calculada.
class Seminar {
reserved = 0; // Número de inscripciones
constructor(capacity) {
this.capacity = capacity; // Capacidad
}
// Se calcula con la capacidad y las inscripciones del momento, cada vez que se lee
get remainingSeats() {
return this.capacity - this.reserved;
}
}
const seminar = new Seminar(30);
console.log(seminar.remainingSeats); // 30 (se lee sin paréntesis)
// Leerlo otra vez después de que se inscriben 12 personas
seminar.reserved = 12;
console.log(seminar.remainingSeats); // 18
El 30 de la fila de arriba se calculó una sola vez, en el new, y se guardó. El getter de la fila de abajo no guarda nada y ejecuta la expresión del return cada vez que lees seminar.remainingSeats, así que, por muchas veces que cambies reserved, no necesitas agregar una línea que recalcule los lugares disponibles.
Llamar a un getter con paréntesis lanza un TypeError
Un getter se escribe como un método, pero al usarlo no se ponen paréntesis. Si escribes seminar.remainingSeats(), estás llamando como función al 18 que se devolvió, y eso lanza TypeError: seminar.remainingSeats is not a function.
Comprobar los valores asignados — set y validación
Cuando se acerca la fecha del seminario, puede que la sede te permita subir la capacidad de 30 a 40. Podrías escribir una comprobación con if y throw en el constructor, pero solo se ejecuta en el new. Si después asignas seminar.capacity = 5, un valor menor que las 12 inscripciones, nada lo impide y los lugares disponibles aparecen como -7.
Un setter (un método de una clase con set delante, que se ejecuta cuando se asigna la propiedad) recibe el valor asignado en su parámetro value. El valor en sí lo guardas con otro nombre, como _capacity, y lo devuelves desde un getter con el nombre de la propiedad, get capacity.
El _ al principio es una convención que significa «no lo toques directamente desde afuera».
class Seminar {
constructor(capacity, reserved) {
this.reserved = reserved; // Número de inscripciones
this._capacity = capacity; // El valor de la capacidad en sí va en _capacity
}
get capacity() { return this._capacity; }
set capacity(value) { // El valor asignado llega en value
if (value < this.reserved) {
throw new Error(`La capacidad no puede ser menor que las ${this.reserved} inscripciones: ${value}`);
}
this._capacity = value;
}
}
const seminar = new Seminar(30, 12);
seminar.capacity = 40; // Se ejecuta set capacity y guarda 40
console.log(seminar.capacity); // 40 (se ejecuta get capacity)
seminar.capacity = 5; // Error: La capacidad no puede ser menor que las 12 inscripciones: 5
=, se ejecuta set; cuando se lee en cualquier otro lugar, se ejecuta get. Un valor que no pasa la comprobación nunca llega a _capacity.La línea ③ lanza un error, así que, si no la envuelves en try, las líneas siguientes no se ejecutan. Mientras asignes a capacity, el valor pasa por el setter, pero si escribes directamente seminar._capacity = 5, se salta la comprobación. En el siguiente artículo verás cómo evitarlo.
Asignar al mismo nombre dentro de un setter lanza un RangeError
Si escribes this.capacity = value; dentro de set capacity(value), esa asignación vuelve a llamar al setter, que vuelve a asignar, y así sucesivamente hasta lanzar RangeError: Maximum call stack size exceeded. Guarda el valor con otro nombre, como _capacity.
Pasar por el setter también al crear — asignar en el constructor
En el Seminar de arriba, el constructor guarda el valor directamente en _capacity, así que new Seminar(5, 12) crea una instancia sin ninguna comprobación. Si escribes el mismo if también en el constructor, tienes que corregir dos lugares cada vez que cambia la condición, y si olvidas uno, new y la asignación dan resultados distintos.
Si dentro del constructor asignas al nombre sin _, como en this.capacity = capacity, el setter también se ejecuta en esa línea. La comprobación queda en un solo lugar, el setter, y tanto los valores que pasas a new como los que asignas después se comprueban con la misma condición.
class Seminar {
constructor(capacity, reserved) {
this.reserved = reserved; // Primero guarda las inscripciones que usa la comprobación
this.capacity = capacity; // El nombre no tiene _, así que se ejecuta set capacity
}
get capacity() { return this._capacity; }
set capacity(value) {
if (value < this.reserved) {
throw new Error(`La capacidad no puede ser menor que las ${this.reserved} inscripciones: ${value}`);
}
this._capacity = value;
}
}
try {
new Seminar(5, 12); // new también pasa por la comprobación del setter
} catch (error) {
console.log(error.message); // La capacidad no puede ser menor que las 12 inscripciones: 5
}
new Seminar(5, 12)— llama al constructor- La excepción del setter sube hasta aquí y no se devuelve ninguna instancia
this.reserved = 12— guarda las inscripciones antes de la comprobaciónthis.capacity = 5— el nombre no tiene_, así que llama a set capacity
valuees 5 ythis.reservedes 125 < 12, así que hacethrow— no entra nada en_capacity
this.capacity = 5 llama a set capacity. La excepción sale del constructor y llega al try.Si mueves this.reserved = reserved debajo de this.capacity = capacity, this.reserved es undefined cuando se ejecuta el setter. En una comparación numérica, undefined se convierte en NaN, y 5 < NaN es false, así que la instancia se crea con una capacidad de 5.
Impedir la asignación — propiedades con solo un getter
Los lugares disponibles dependen de la capacidad y de las inscripciones. Si el código de la página puede escribir seminar.remainingSeats = 100, la página muestra 100 lugares disponibles con una capacidad de 30 y 12 inscripciones, y los tres valores dejan de cuadrar.
Una propiedad que tiene solo un getter, sin un setter con el mismo nombre, es de solo lectura (se puede leer, pero no asignar). En el cuerpo de una clase y en modo estricto (un modo que convierte algunos errores en excepciones), que es el que usa este entorno, la línea que le asigna un valor lanza un TypeError. Fuera de esos casos, la asignación se ignora sin avisar.
class Seminar {
constructor(capacity, reserved) {
this.capacity = capacity;
this.reserved = reserved;
}
get remainingSeats() { return this.capacity - this.reserved; } // Sin setter
}
const seminar = new Seminar(30, 12);
try {
seminar.remainingSeats = 100; // Asignar a un nombre que no tiene set
} catch (error) {
console.log(error.name); // TypeError
}
console.log(seminar.remainingSeats); // 18 (igual que antes de la asignación)
// Para cambiar los lugares disponibles, cambia las inscripciones de las que dependen
seminar.reserved = 20;
console.log(seminar.remainingSeats); // 10
La fila de abajo lanza un error porque un nombre con solo un getter no tiene dónde guardar el valor asignado, y tampoco se crea una clave nueva en la instancia. La tabla de abajo resume las formas que usamos en el Seminar de este artículo, junto con un método para comparar.
| Forma | Cómo se usa | Sirve para |
|---|---|---|
| Propiedad normal (reserved) | seminar.reserved = 20 la guarda tal cual | Valores sin comprobar |
| Solo getter (remainingSeats) | Solo lectura: seminar.remainingSeats | Valores calculados de otros |
| Getter y setter (capacity) | seminar.capacity = 40 se comprueba | Valores a comprobar al asignar |
| Método (p. ej., reserve(count)) | Con paréntesis y argumentos | Resultados según los argumentos |
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2Si set capacity contiene this.capacity = value; y asignas 40, ¿qué pasa?
Pregunta 3¿Qué pasa si asignas 100 a seminar.remainingSeats, que solo tiene un getter?