static: lo que comparten todos los objetos
En resumen: Un campo o un método static pertenece a la clase, no al objeto: todos los objetos ven un único valor compartido, por ejemplo un contador de los objetos creados.
Problema
Todos los productos son el n.º 1
La tienda quiere que cada producto nuevo reciba el número siguiente: 1, 2, 3… Pones un contador #nextId en la clase. El constructor le da al producto el número actual y aumenta el contador:
class Product { #nextId = 1; #id; #name;
constructor(name) { this.#name = name; this.#id = this.#nextId; this.#nextId++; }
toString() { return `n.º ${this.#id} ${this.#name}`; }}
const bread = new Product("Pan");const milk = new Product("Leche");const coffee = new Product("Café");console.log(String(bread));console.log(String(milk));console.log(String(coffee));n.º 1 Pann.º 1 Lechen.º 1 CaféTodos son el n.º 1. La razón es que #nextId es un campo del objeto, igual que el nombre. Cada producto nuevo recibe su propio contador, que empieza en 1. El contador del pan nunca llega a la leche.
La idea
Un campo de la clase, no del objeto
El contador no es parte de un producto. Lo comparten todos los productos. Añade static:
static #nextId = 1;Un campo static es un campo de la clase. Hay uno solo, sin importar cuántos objetos crees, y todos los objetos lo comparten. En el constructor llegas a él a través del nombre de la clase: Product.#nextId.
class Product { static #nextId = 1; #id; #name;
constructor(name) { this.#name = name; this.#id = Product.#nextId; Product.#nextId++; }
toString() { return `n.º ${this.#id} ${this.#name}`; }}
const bread = new Product("Pan");const milk = new Product("Leche");const coffee = new Product("Café");console.log(String(bread));console.log(String(milk));console.log(String(coffee));n.º 1 Pann.º 2 Lechen.º 3 CaféEl pan toma el 1 y deja el contador en 2. La leche ve el mismo contador, toma el 2 y lo deja en 3.
static #nextId = 1;
constructor(name) { this.#name = name; this.#id = Product.#nextId; Product.#nextId++;}
const bread = new Product("Pan");const milk = new Product("Leche");const coffee = new Product("Café");Paso a paso, en palabras
- #nextId es static: un campo en la clase, no uno en cada objeto.
- new Product("Pan"): un objeto nuevo. El constructor guarda el nombre.
- this.#id = Product.#nextId: el objeto toma el número actual, 1.
- Product.#nextId++ cambia el contador compartido: ahora es 2.
- Leche: el mismo contador. Toma el 2 y lo deja en 3.
- Café: toma el 3 y lo deja en 4.
- Hay un solo contador para todos, así que los números van en orden.
Un campo static no se ve a través de un objeto. Desde fuera llegas a él a través del nombre de la clase (Order.count):
class Order { static count = 0;}
const order = new Order();console.log(Order.count, order.count);0 undefinedPruébalo
Compartido y propio
¿Qué imprimirá el programa?
class Order { static count = 0; items = 0;
constructor() { Order.count++; this.items++; }}
const a = new Order();const b = new Order();const c = new Order();console.log(Order.count, c.items);count es static: un contador para todos los pedidos, aumentado tres veces. items es un campo del objeto: cada pedido tiene el suyo, aumentado una vez.La idea
Un método static: trabajar sin objeto
Lo mismo vale para los métodos. Un método static es un método de la clase. Lo llamas a través del nombre de la clase, sin new:
class Calculator { static add(a, b) { return a + b; }
static average(values) { let sum = 0; for (const v of values) { sum += v; } return sum / values.length; }}
console.log(Calculator.add(10, 20));const grades = [5, 6, 4, 6];console.log(Calculator.average(grades));console.log(Math.max(3, 8));305.258Calculator.add no necesita un objeto: todo lo que necesita llega como parámetros. Los métodos de Math también son así: Math.max, Math.sqrt. De Math no se crean objetos, y new Math() detiene el programa:
TypeError: Math is not a constructorJavaScript no tiene main: el programa son las líneas del archivo, de arriba abajo. Por eso las funciones del módulo «Funciones» no necesitaban una clase.
Más precisamente, para curiosos
Lo contrario también funciona: en un método static puedes crear un objeto y llamar a sus métodos.
Pruébalo
Encuentra el error
Elige la línea con el error.
label() lee this.#name, y el nombre pertenece a un producto concreto. Un método static pertenece a la clase, así que el objeto tea ni siquiera tiene ese método:TypeError: tea.label is not a functionstatic: la etiqueta pertenece al producto, así que es un método de instancia.La idea
Constantes
El IVA es del 20 % para todos los productos, y no cambia mientras el programa se ejecuta. Un valor así es una constante:
const VAT_RATE = 0.20;
class Prices { static withVat(price) { return price * (1 + VAT_RATE); }}
console.log(Prices.withVat(50));console.log(`IVA: ${VAT_RATE * 100}%`);60IVA: 20%const significa que la variable se fija una vez y no se puede cambiar. La pones fuera de la clase, al principio del archivo. Los nombres de las constantes van en mayúsculas con guiones bajos: VAT_RATE.
Si alguien intenta cambiarla,
VAT_RATE = 0.25;el programa se detiene:
TypeError: Assignment to constant variable.Ganas dos veces: el número 0.20 está escrito en un solo lugar con un nombre claro, y nadie puede cambiarlo por error.
Más precisamente, para curiosos
Un campo static de la clase no es una constante: se puede cambiar desde fuera.
class Prices { static VAT_RATE = 0.20;}
Prices.VAT_RATE = 0.25;console.log(Prices.VAT_RATE);0.25A veces el valor es distinto para cada objeto, pero no debe cambiar después de crear el objeto. El número del producto es así. Hazlo un campo privado con un getter y sin setter (la lección «Encapsulamiento»):
class Product { static #nextId = 1; #id; #name;
constructor(name) { this.#name = name; this.#id = Product.#nextId; Product.#nextId++; }
get id() { return this.#id; }}coffee.id lee el número, y coffee.id = 5 no lo cambia: el campo #id es privado y solo tiene un getter.
Pruébalo
¿Qué falta?
MONTHS = 12;const: el valor se fija una vez y no se puede cambiar.La idea
Cuándo static y cuándo no
Pregúntate: «¿Esto pertenece a un objeto concreto?»
- Sí (el saldo de la cuenta, el nombre del producto, retirar de una cuenta): un campo o un método del objeto, sin
static. - No (el número de todas las cuentas, el IVA, la suma de dos números):
static.
Un error frecuente es hacerlo todo static para no tener que crear objetos. Entonces todos los «objetos» comparten los mismos datos, y vuelves al problema de los arrays paralelos de la primera lección.
Ejercicio
Lo que comparten todos los objetos
Resuelve las tareas en tu editor.
1. Pedidos. Una clase Order con un cliente y un importe. Cada pedido nuevo recibe el número siguiente de un contador static. Un getter static count dice cuántos pedidos se han creado.
const orders = [ new Order("María", 45.5), new Order("Pablo", 120), new Order("Elena", 18.9),];for (const o of orders) { console.log(String(o));}console.log(`Pedidos: ${Order.count}`);Pedido n.º 1: María, 45.5 €Pedido n.º 2: Pablo, 120 €Pedido n.º 3: Elena, 18.9 €Pedidos: 32. Calculadora. Una clase Calculator con métodos static para sumar, restar y multiplicar números enteros, y para dividir números decimales.
console.log(Calculator.add(10, 20));console.log(Calculator.subtract(10, 15));console.log(Calculator.multiply(6, 7));console.log(Calculator.divide(5, 2));30-5422.53. Cambio de divisas. Una clase Converter con constantes para el tipo de cambio: 1 € = 1,08 dólares estadounidenses y 1 € = 0,85 libras esterlinas (los tipos son de ejemplo). Métodos static convierten un importe en euros a dólares y a libras. Para 20 €:
20 EUR = 21.6 USD20 EUR = 17 GBPMás tareas
- Cuentas numeradas. Añade a la
Accountde la lección «Encapsulamiento» un número de cuenta que sale de un contador static y un getter sin setter para el número. - Cambio en los dos sentidos. Añade a
Convertermétodos que convierten de dólares y de libras a euros. Convierte 20 € a dólares y de vuelta a euros. ¿Obtienes exactamente 20? - Un límite. La tienda acepta como mucho 3 productos en un carrito. Añade una constante para eso, y haz que el método de añadir rechace el cuarto.
En resumen
El módulo en resumen
- Una clase es tu propio tipo, y un objeto es una instancia concreta de ella, creada con
new. La variable apunta al objeto. Clases y objetos constructorse asegura de que el objeto se cree completo.thises el objeto actual. Constructores y this- Los métodos de instancia trabajan con los campos del objeto.
toString()dice cómo se ve como texto. Métodos de instancia - Los campos
#son privados. Desde fuera llegas a ellos solo a través degetyset, que comprueban. Encapsulamiento statices lo que comparte la clase: contadores y métodos auxiliares. Las constantes sonconst.
Datos y reglas en un solo lugar que nadie puede saltarse: eso es la programación orientada a objetos. En el siguiente módulo, las clases empezarán a compartir código entre sí.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Clases y objetos · Constructores y this · Métodos de instancia · Encapsulamiento
- Continúa en: Principios de la POO · Lanzar excepciones y crear las tuyas · Expresiones regulares: grupos y reemplazo · Datos en archivos
- Ejercicios sobre el tema: Clases y objetos