Ir al contenido
wannacode

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));
Salida
n.º 1 Pan
n.º 1 Leche
n.º 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.

shop.js
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));
Salida
n.º 1 Pan
n.º 2 Leche
n.º 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
  1. #nextId es static: un campo en la clase, no uno en cada objeto.
  2. new Product("Pan"): un objeto nuevo. El constructor guarda el nombre.
  3. this.#id = Product.#nextId: el objeto toma el número actual, 1.
  4. Product.#nextId++ cambia el contador compartido: ahora es 2.
  5. Leche: el mismo contador. Toma el 2 y lo deja en 3.
  6. Café: toma el 3 y lo deja en 4.
  7. 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);
Salida
0 undefined

Prué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);
Respuesta

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));
Salida
30
5.25
8

Calculator.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:

Error
TypeError: Math is not a constructor

JavaScript 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

tea.label() detiene el programa. ¿Qué línea tiene el error?

Elige la línea con el error.

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}%`);
Salida
60
IVA: 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:

Error
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);
Salida
0.25

A 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?

Un año tiene 12 meses, y eso no va a cambiar. ¿Qué va en el hueco?
 MONTHS = 12;

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.

El programa
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}`);
Salida
Pedido n.º 1: María, 45.5 €
Pedido n.º 2: Pablo, 120 €
Pedido n.º 3: Elena, 18.9 €
Pedidos: 3

2. Calculadora. Una clase Calculator con métodos static para sumar, restar y multiplicar números enteros, y para dividir números decimales.

El programa
console.log(Calculator.add(10, 20));
console.log(Calculator.subtract(10, 15));
console.log(Calculator.multiply(6, 7));
console.log(Calculator.divide(5, 2));
Salida
30
-5
42
2.5

3. 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 €:

Salida
20 EUR = 21.6 USD
20 EUR = 17 GBP

Más tareas

  • Cuentas numeradas. Añade a la Account de 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 Converter mé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
  • constructor se asegura de que el objeto se cree completo. this es 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 de get y set, que comprueban. Encapsulamiento
  • static es lo que comparte la clase: contadores y métodos auxiliares. Las constantes son const.

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

1. Creas 5 objetos de una clase con el campo static count = 0. ¿Cuántas copias de count hay en la memoria?
2. ¿Qué NO puede hacer un método static?
3. Order tiene static count = 0. ¿Qué da order.count para un objeto order?
4. ¿Qué pasa con VAT_RATE = 0.25; si VAT_RATE está declarada con const?
5. ¿Qué es un buen candidato para un método static?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·