Aprende leyendo en orden

Miembros de instancia y static — valores y métodos de la propia clase

Cómo static pone valores y métodos en la propia clase: contadores con campos static, métodos de fábrica que crean instancias desde JSON, constantes compartidas y cuándo usar miembros de instancia.

Para numerar paquetes 1, 2, 3 en el orden en que los creas, tienes que llevar la cuenta de cuántos llevas creados. El destinatario cambia de un paquete a otro, pero de esa cuenta y del límite de peso basta con una sola copia, compartida por todos los paquetes.

En este artículo verás static, que pone valores y métodos en la propia clase en lugar de en sus instancias.

Contar paquetes — campos static

Quieres numerar los paquetes en el orden en que se crean. Si escribes el campo de clase count = 0;, lo aumentas con this.count += 1 en el constructor y lo usas como número, todos los paquetes reciben el número 1, porque el campo vuelve a empezar en 0 en cada new.

Un campo static (una propiedad que se declara dentro de la clase con la palabra clave static y de la que existe una sola copia, en la propia clase) se lee y se escribe a través del nombre de la clase, como Parcel.count.

En cambio, las propiedades que tiene cada instancia, como this.number, y los métodos escritos sin static se llaman miembros de instancia.

class Parcel {
  // Cantidad de paquetes creados hasta ahora, guardada una sola vez en la clase
  static count = 0;

  constructor(recipient) {
    Parcel.count += 1;               // Aumenta en uno el count de la clase
    this.number = Parcel.count;      // Usa la cuenta de este momento como número del paquete
    this.recipient = recipient;
  }
}

// Crea dos paquetes
const first = new Parcel("Ana");
const second = new Parcel("Carlos");

console.log(first.number, second.number);   // 1 2
console.log(Parcel.count);                  // 2
console.log(first.count);                   // undefined
Dónde viven count y los números de paquete
Parcel (la propia clase)
  • static count: 1 después del primer new, 2 después del segundo
  • Se lee y se escribe como Parcel.count
Instancias creadas con new
first
  • number: 1 (la cuenta al crearla)
  • recipient: «Ana»
  • No hay count: first.count es undefined
second
  • number: 2 (la cuenta al crearla)
  • recipient: «Carlos»
Hay un solo count, en el recuadro de la clase, y ninguno en los de las instancias. Si lo lees desde una instancia, obtienes undefined.

Cada new aumenta en uno el mismo count del recuadro de la clase y copia su valor actual en el number propio de la nueva instancia. Aunque después count suba a 2, first.number se queda en 1, el valor que recibió al crearse.

Numera las respuestas de una encuesta en el orden en que llegan. answerTexts ya está declarada.

① Define SurveyAnswer, que recibe el texto de la respuesta. Cada vez que se crea una respuesta, aumenta en uno un contador compartido por todas las respuestas y úsalo como número de esa respuesta.

② Convierte los tres textos en instancias de SurveyAnswer y ponlas en answers.

③ Muestra todas las respuestas con el formato «A-001 texto de la respuesta».

④ Muestra «Respuestas: N» y «Número de la primera respuesta: N».

(Si se ejecuta correctamente, aparecerá una explicación).

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Crear paquetes desde JSON — métodos static

Cuando cargas un paquete guardado, recibes un texto JSON con el destinatario y el peso. Si cada pantalla que carga paquetes tiene su propio JSON.parse y su propio new Parcel(...), un cambio en los nombres de las claves del JSON te obliga a corregir todas esas pantallas.

Pero cuando creas un paquete a partir de JSON, todavía no hay ninguna instancia sobre la que llamar a un método. Un método static (un método definido con static que se llama a través del nombre de la clase, sin crear una instancia) se puede llamar como Parcel.fromJSON(text). Un método static como fromJSON, que crea y devuelve una instancia, se llama método de fábrica.

class Parcel {
  constructor(recipient, weightKg) {
    this.recipient = recipient;
    this.weightKg = weightKg;
  }

  label() {
    return `${this.recipient} (${this.weightKg} kg)`;
  }

  // Recibe un texto JSON, crea una instancia de Parcel y la devuelve
  static fromJSON(text) {
    const data = JSON.parse(text);
    return new Parcel(data.recipient, data.weightKg);
  }
}

const text = '{"recipient":"Ana","weightKg":2}';
const parcel = Parcel.fromJSON(text);        // Se llama a través del nombre de la clase
console.log(parcel.label());                 // Ana (2 kg)
De un texto JSON a un Parcel
Se llama aParcel.fromJSON(text)Se lee conJSON.parse(text)Crea y devuelvenew Parcel(…)text es solouna cadenaTiene propiedadespero no label()Instancia quepuede llamara label()
JSON.parse te da un objeto simple con propiedades, pero sin métodos. Solo después de pasar por new se convierte en una instancia que puede llamar a label().

La última línea de fromJSON es new Parcel(...), así que el cuerpo del constructor se ejecuta como siempre. Si pones validaciones con throw en el constructor, los paquetes que cargas desde JSON pasan por las mismas validaciones que los que se ingresan en pantalla.

No puedes llamar a fromJSON sobre una instancia

Si lo llamas sobre una instancia, como en parcel.fromJSON(text), se lanza TypeError: parcel.fromJSON is not a function. A diferencia de label(), un método static solo se puede llamar a través del nombre de la clase. Cómo funciona esto lo verás en el artículo sobre prototipos.

Recupera productos guardados del carrito a partir de textos JSON. CartItem y savedTexts ya están declarados.

① Agrega un método fromJSON que cree y devuelva una instancia de CartItem a partir de un texto JSON.

② Usa ① para convertir las dos entradas de savedTexts en instancias de CartItem.

③ Muestra «Nombre x cantidad = subtotal yenes» para cada producto.

④ Muestra la suma de los subtotales con el formato «Total: N yenes».

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Un límite fijo para todos los paquetes — constantes static

El límite de peso de 30 kg se usa tanto en el aviso del formulario de ingreso, que se muestra antes de que exista ningún paquete, como en el cálculo de la capacidad restante de cada paquete. Si pones el límite en un campo de instancia, tendrías que crear un paquete sin destinatario ni peso solo para mostrar el aviso en el formulario.

Una constante static (un campo static con un valor que no cambia mientras se ejecuta el programa) se nombra todo en mayúsculas, con las palabras separadas por guiones bajos, como en static MAX_WEIGHT_KG = 30;. Tanto fuera de la clase como dentro de un método, se lee a través del nombre de la clase: Parcel.MAX_WEIGHT_KG.

class Parcel {
  // El límite común a todos los paquetes se guarda una sola vez en la clase con static
  static MAX_WEIGHT_KG = 30;

  constructor(weightKg) {
    this.weightKg = weightKg;
  }

  // Cuántos kg más caben antes del límite
  remainingKg() {
    return Parcel.MAX_WEIGHT_KG - this.weightKg;
    // Si escribes this.MAX_WEIGHT_KG - this.weightKg, devuelve NaN
  }
}

// Se puede leer incluso antes de que exista una instancia
console.log(`Hasta ${Parcel.MAX_WEIGHT_KG} kg por paquete`);   // Hasta 30 kg por paquete
const parcel = new Parcel(12);
console.log(parcel.remainingKg());                             // 18
La forma de leer el límite decide el resultado
static MAX_WEIGHT_KG= 30Aviso del form.:Parcel.MAX_WEIGHT_KGEn remainingKg:Parcel.MAX_WEIGHT_KGEn remainingKg:this.MAX_WEIGHT_KGLee 30 sininstanciaCalcula30 - 12No está en lainstancia:undefinedMuestra "Hasta30 kg por paquete"Devuelve 18undefined - 12devuelve NaN
Las columnas izquierda y central leen el mismo 30, haya o no una instancia. Solo la columna derecha, que lee desde this, termina en NaN.

La columna derecha termina en NaN porque this dentro de un método es la instancia, y las constantes static no están en la instancia. undefined - 12 no lanza ningún error, así que solo te das cuenta cuando el peso restante que se muestra no tiene sentido.

Aunque esté en mayúsculas, se puede reasignar

Un nombre en mayúsculas es solo una convención que significa «no cambies esto»; si asignas Parcel.MAX_WEIGHT_KG = 50;, el valor cambia. Si defines solo un getter, como en static get MAX_WEIGHT_KG() { return 30; }, asignarle un valor lanza un TypeError (los getters se ven en el siguiente artículo).

Revisa nombres de usuario según reglas de longitud. registeredNames ya está declarada.

① Dale a Account una longitud mínima de nombre de 3 y una máxima de 12, compartidas por todas las cuentas.

② Agrega isValidName, que devuelve si la longitud del nombre está dentro del rango.

③ Convierte cada nombre en un Account y muestra «nombre: válido» o «nombre: no válido».

④ Supón que otra parte del código cambió por error la longitud máxima a 20. Agrega esa asignación y vuelve a mostrar los resultados con el mismo formato que en ③.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Decidir dónde vive un valor — static frente a instancia

Cuando conoces static, puede parecer práctico poner en la clase un valor como el destinatario para leerlo desde un solo lugar, Parcel.recipient. En una prueba rápida que crea un solo paquete, se muestra el mismo destinatario tanto si es static como si está en this, así que no notarás la diferencia.

Decide dónde vive un valor según si cambia de un elemento a otro. Un valor puesto en this existe por separado en cada instancia que crea new, mientras que un valor static existe una sola vez en la clase, crees los paquetes que crees.

// Destinatario guardado en static
class SharedParcel {
  static recipient = "";
  constructor(recipient) { SharedParcel.recipient = recipient; }
  label() { return `Para: ${SharedParcel.recipient}`; }
}

// Destinatario guardado en la instancia
class Parcel {
  constructor(recipient) { this.recipient = recipient; }
  label() { return `Para: ${this.recipient}`; }
}

const sharedFirst = new SharedParcel("Ana");
new SharedParcel("Carlos");
console.log(sharedFirst.label());   // Para: Carlos

const first = new Parcel("Ana");
new Parcel("Carlos");
console.log(first.label());         // Para: Ana
¿El segundo new cambia el destinatario del primer paquete?
Destinatario enstatic recipientEl 2.º newasigna CarlosSe sobrescribeel único recipientsharedFirst.label()da Para: CarlosDestinatario enthis.recipientEl 2.º new esotra instanciaEl 1.er recipientsigue siendo Anafirst.label()da Para: Ana
En la fila de arriba, el destinatario del segundo paquete sobrescribe el único valor que hay en la clase. Pon en this los valores que cambian de un elemento a otro, para que cada instancia tenga el suyo.

sharedFirst no tiene destinatario propio, y label() lee SharedParcel.recipient tal como está en el momento de la llamada, así que muestra «Carlos», el último valor asignado. La tabla de abajo resume los miembros que usamos en las clases de paquetes de este artículo: cómo se escribe cada tipo y cómo se usa.

Qué guardaCómo se escribe y se usaEjemplos del artículo
Un valor por elementoEn this, se lee de la instancianumber / recipient / weightKg
Método con valores propiosSin static, se llama en la instancialabel() / remainingKg()
Un valor para todosCon static, se lee por la clasecount / MAX_WEIGHT_KG
Lógica sin instanciaCon static, se llama por la clasefromJSON(text)
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1Parcel tiene static count = 0;. ¿Qué obtienes al leer first.count en una instancia first?

Pregunta 2¿Qué pasa si llamas a un método static sobre la instancia parcel, como en parcel.fromJSON(text)?

Pregunta 3Una clase guarda el destinatario en un campo static. Si creas un paquete para «Ana» y después otro para «Carlos», ¿qué destinatario muestra el label() del primero?