Todas las novedades de ECMAScript (2021 – 2025)

Todas las novedades de ECMAScript (2021 – 2025)

Si programas en JavaScript, sabes que el lenguaje no para de evolucionar. Desde 2021 hasta 2025, ECMAScript ha traído cambios muy interesantes: nuevas formas de escribir clases, métodos más potentes para arrays, mejoras en objetos, y propuestas que prometen hacer nuestro código más limpio y eficiente.

En este artículo voy a intentar resumir todo lo que necesitas saber, usando ejemplos claros y explicaciones que van al grano.


ECMAScript 2021 (ES12)

Operadores lógicos de asignación

Permiten asignar un valor a una variable si se cumple una condición lógica.

let a = null; 
a ||= 'valor por defecto'; // "valor por defecto"

Separadores numéricos

Mejoran la legibilidad de números grandes.

const billon = 1_000_000_000;

String.prototype.replaceAll()

Permite reemplazar todas las apariciones de una subcadena.

"foo bar foo".replaceAll("foo", "baz"); // "baz bar baz"

Promise.any()

Resuelve con la primera promesa cumplida.

Promise.any([
   Promise.reject("error"),
   Promise.resolve("éxito")
]); // "éxito"

WeakRef y FinalizationRegistry

Permiten la referencia débil a objetos y su manejo al ser recolectados, lo que resulta útil en tareas como cacheado o manejo de recursos sin evitar que el recolector de basura los libere.

  • WeakRef: crea una referencia «débil» a un objeto, que no impide que el GC (garbage collector) lo elimine.

  • FinalizationRegistry: te permite registrar una función que se ejecuta cuando el objeto asociado es recolectado, pero no garantiza cuándo se ejecutará la función, solo que lo hará en algún momento después de que el objeto se recolecte. No es recomendable para lógica crítica.

class Cache {
  constructor() {
    this.cache = new Map();
    this.finalizationRegistry = new FinalizationRegistry((key) => {
      console.log(`Objeto con clave "${key}" fue recolectado.`);
      this.cache.delete(key); // Limpia la entrada del mapa
    });
  }

  set(key, value) {
    this.cache.set(key, new WeakRef(value));
    this.finalizationRegistry.register(value, key);
  }

  get(key) {
    const ref = this.cache.get(key);
    return ref?.deref(); // Devuelve el objeto si aún no ha sido recolectado
  }
}

// Uso
let user = { name: "Sparrow" };
const cache = new Cache();

cache.set("user1", user);

console.log(cache.get("user1")?.name); // "Sparrow"

user = null; // El objeto ahora puede ser recolectado. Después de cierto tiempo, el GC lo elimina y FinalizationRegistry se activa

ECMAScript 2022 (ES13)

Campos y métodos privados en clases

Restricción de acceso mediante el prefijo #

class Persona {
   #nombre;
   constructor(nombre) {
      this.#nombre = nombre;
   }
   #saludar() {
      console.log(`Hola, soy ${this.#nombre}`);
   }
}

Array.prototype.at()

Accede a elementos por índices positivos o negativos.

const arr = [1, 2, 3]; 
arr.at(-1); // 3

Object.hasOwn()

Verifica si un objeto tiene una propiedad propia (sin herencia).

Object.hasOwn({ a: 1 }, 'a'); // true

Error.cause

Permite encadenar errores con contexto adicional.

try {
   throw new Error('Error bajo nivel');
} catch (err) {
   throw new Error('Error alto nivel', { cause: err });
}

RegExp Match Indices

Obtiene posiciones de coincidencias.

const m = /a(b)c/d.exec("abc");
m.indices; // [[0,3],[1,2]]

ECMAScript 2023 (ES14)

Array.prototype.findLast() y findLastIndex()

Buscan desde el final del array.

[1, 2, 3, 4].findLast(x => x % 2 === 0); // 4

Métodos de arrays inmutables

Retornan nuevas versiones del array:

const arr = [3, 1, 2];
arr.toSorted();      // [1, 2, 3]
arr.toReversed();    // [2, 1, 3]
arr.toSpliced(1, 1); // [3, 2]
arr.with(1, 99);     // [3, 99, 2]

Símbolos como claves en WeakMap

Ya se pueden usar símbolos como claves en un WeakMap, siempre que no sean símbolos registrados (es decir, creados con Symbol.for()).

Antes de esta mejora, solo se permitían objetos como claves, y usar símbolos directamente generaba error.

Esto permite usar símbolos únicos (no registrados globalmente) como identificadores privados o claves internas en estructuras como WeakMap, sin necesidad de envolverlos en objetos.

const wm = new WeakMap();

const sym = Symbol(); // símbolo no registrado
const obj = { secreto: "valor oculto" };

wm.set(sym, obj); // ¡Ahora permitido!

console.log(wm.get(sym)); // { secreto: "valor oculto" }

Hashbang grammar

A partir de ES2023, JavaScript admite oficialmente el uso de una línea hashbang (#!) al comienzo de los archivos, al estilo de los scripts de Unix.

Esta nueva característica nos permite que archivos JavaScript sean ejecutables directamente desde la terminal en sistemas como Linux o macOS, sin necesidad de invocar manualmente node.

Esto puede ser muy útil para crear nuestro propios CLI (Command Line Interface) en node.

#!/usr/bin/env node console.log("¡Hola desde un script ejecutable de Node.js!");

¿Qué hace #!/usr/bin/env node?

  • Es una directiva para el sistema operativo, no para JavaScript.

  • Le dice al sistema que use el ejecutable de node para correr el archivo.

  • El motor JavaScript ignora esa línea gracias a la nueva sintaxis soportada por el estándar

Chequeo ergonómico de campos privados

Permite verificar si un objeto tiene un campo privado:

if (#miCampo in obj) { ... }

ECMAScript 2024 (ES15)

Object.groupBy() y Map.groupBy()

Agrupan elementos según una función de agrupamiento.

const arr = [1, 2, 3, 4, 5];
Object.groupBy(arr, x => x % 2 === 0 ? 'pares' : 'impares');

Pipeline operator (|>)

Permite componer funciones de forma legible.

const payload = [{ name: 'A', active: true }, { name: 'X', active: true }, { name: 'H', active: false }, { name: 'N', active: true }, { name: 'J', active: false }];

// Forma normal concatenando funciones
const fnComplete = users => users
        .filter(user => user.active)
        .map(user => user.name.toUpperCase())
        .sort()
        .join(', ');
console.log(fnComplete(payload)); // A, N, X

// Usando |>
const getActiveUser = users => users.filter(user => user.active);
const getUserNameToUppercase = users.map(user => user.name.toUpperCase());
const sortUsers = users => users.sort();
const usersToString = users => users.join(', ');
const x = users |> getActiveUser|> getUserNameToUppercase |> sortUsers |> usersToString;
console.log(x); // A, N, X

Para usar esta característica es necesario instalar el plugin plugin-proposal-pipeline-operator.

npm install --save-dev @babel/plugin-proposal-pipeline-operator

También deberás añadir la configuración en tu archivo .babelrc

{
  "plugins": [["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }]]
}

Nuevos métodos en Set

Operaciones como en matemáticas:

set1.union(set2);
set1.intersection(set2);
set1.difference(set2);

ECMAScript 2025 (ES16)

Los detalles finales de ES2025 aún no están cerrados oficialmente, ya que el proceso del comité TC39 sigue en marcha. Sin embargo, es probable que la versión incluya propuestas que actualmente se encuentran en etapa 3 o 4, como:

  • Nuevas mejoras de ergonomía en clases y estructuras de datos.

  • Posibles avances en tipos integrados opcionales.

  • Funciones integradas más expresivas y reutilizables.

  • Mejoras de rendimiento y consistencia del lenguaje.

Puedes seguir el estado más actualizado de las propuestas activas y su progreso directamente en las siguientes web: https://tc39.es/https://tc39.es/ecma262/y https://github.com/tc39/proposals

A medida que estas propuestas avancen a Stage 4, pasarán a formar parte del borrador oficial de ECMAScript.

El estándar se publica normalmente cada junio, por lo que ES2025 se cerrará en la primera mitad del año.

Conclusión

ECMAScript evoluciona rápidamente para adaptarse a un ecosistema de desarrollo más potente, expresivo y seguro.

Estas mejoras no solo aportan sintaxis más limpia, sino también patrones más robustos que permiten desarrollar aplicaciones modernas con mayor confianza.