La versión de Javascript ES12 o ECMAScript 2021, desarrollada en GitHub con la ayuda de la comunidad ECMA International, es ya una realidad con geniales inclusiones a las que dar fuegote.

Ahondamos en el abanico de novedades que podremos encontrar aquí, integradas en el motor Google Chrome V8 y que puedes consultar en el repositorio de funcionalidades ya integradas por año en Javascript.

Allá vamos 🔥 🔥

Operador de asignación lógica

El operador de asignación lógica combina en una expresión binaria los operadores lógicos && || ?? con una asignación, formalizándola según la naturaleza truthy o falsy del primero de los operandos.

En el caso de &&, la asignación lógica sólo se produce frente a un valor truthy:


let code = 100
let newCode = 200
code &&= newCode 
console.log(code)     // 200

En el caso de ||, la asignación lógica sólo se produce frente a un valor falsy:


let code = 100
let newCode = 200
code ||= newCode 
console.log(code)     // 100

En el caso de ??, la asignación lógica sólo se produce frente a un valor null o undefined, exclusivamente:


let code = undefined
let newCode = 200
code ??= newCode
console.log(code)     // 200

Método .replaceAll()

Presente en el prototipo de las instancias de String, el método replaceAll() reemplaza todas las ocurrencias de una cadena por otra cadena, un comportamiento similar a del actual .replace() con la salvedad de que alcanza por defecto a todas las coincidencias, en vez de limitarse a la primera:


const string = "Tú estás bien, por eso yo estoy bien"
const replacedString1 = string.replace("bien", "mal")     // Limitado al primer match
console.log(replacedString) 
// Tú estás mal, por eso yo estoy bien

const replacedString2 = string.replaceAll("bien", "mal")  // Extensible a todo el string 
console.log(replacedString2) 
// Tú estás mal, por eso yo estoy mal

Podemos asi obviar la expresión regular que permitía simular este comportamiento mediante .replace(), integrada por defecto en el nuevo .replaceAll().

Métodos de clase privados

A los métodos constructores, accesorios, estáticos y prototípicos de las clases se unen los métodos privados, que prefijándolos con una almohadilla verán su alcance limitado a la clase donde están definidos.

En caso de tratar de accederlos, obtendremos un valor undefined con su correspondiente excepción de tipo:


class Invoice {
  
  /* ... */

  #addDiscount(val) {
     this.subtotal -= val
  }
}

const invoice = new Invoice()
invoice.addDiscount(100)   // TypeError: invoice.addDiscount is not a function

Getters y setters privados

Los métodos de acceso nos permitían declarar, a través de la sintaxis get nombrePropiedad() y set nombrePropiedad(), métodos en una clase con un comportamiento de propiedad a efectos externos:


class Invoice {
  
  /* ... */

  get totalInvoice() {
     return (this.cost + this.benefit) * this.tax
  }

  set tax(value) {
     this.tax = value
  }
}

const invoice = new Invoice()
invoice.tax = .21      // manipularía el valor de la propiedad en la clase
invoice.totalInvoice   // retornaría el sumatorio que proceda

La novedad de ES12 reside en limitar el acceso a cualquier método accesorio al prefijarlo con una almohadilla:


class Invoice {
  
  /* ... */

  get #benefit() {
     return 10
  }
}

const invoice = new Invoice()
invoice.benefit   // undefined

Promise.any()

El método .any() se une al objeto global Promise, cuyo argumento en forma de array de promesas será gestionado capturando la respuesta de la primera que sea resuelta de forma satisfactoria, o un array de excepciones en caso de que todas sean rechazadas:


Promise.any([promise1, promise2, promise3])
  .then(value => {
    console.log(`La promesa finalizada con éxito retorna ${value}`)
  }
  .catch(reason => {
    console.log(`Motivos de rechazo de todas las promesas: ${reason.errors.map(err => err.message)}`)
  }

Y claro, la equivalencia async / await mantiene su vigencia en este punto:



try {
  (async function() {
    const value = await Promise.any([promise1, promise2, promise3])
    console.log(`La promesa finalizada con éxito retorna ${value}`)
  })()
} 
catch(reason) {
    console.log(`Motivos de rechazo de todas las promesas: ${reason.errors.map(err => err.message)}`)
}

Promise.any() se diferencia de Promise.race() en que esta segunda forma de gestionar promesas quedaba completada cuando cualquiera de las argumentadas finalizase, tanto si se cumple como si se rechaza, frente a la nueva integración que obviaría una promesa rechazada a la espera de que otra pueda llegar a cumplirse.

AggregateError

La forma de rechazar promesas de Promise.any() es retornando un AggregateError: un nuevo objeto compuesto por diversos errores con las propiedades name y message.

WeakRef

¿Has leído alguna vez la expresión here’s where Javascript gets weird? Allá vamos.

El recolector de basura o garbage collector de Javascript elimina de la memoria las referencias innecesarias durante el transcurso de una aplicación, como es el caso de aquellas variables que han quedado inaccesibles.

El objeto global WeakRef (en referencia a Weak References) establece una referencia débil hacia otro objeto, lo que permitiría optimizar el uso de memoria gracias a un control del recolector de basura, que verá esta referencia desprotegida y podrá eliminarla reclamando así la memoria correspondiente.

Podemos entonces diferenciar desde ahora dos tipos de referencias: las clásicas sólidas (propiedades, variables…) y las débiles, establecidas por este objeto, que abren la puerta a almacenar objetos voluminosos de los que poder prescindir una vez no sean necesarios para la ejecución del programa.

Se vale de un único método .deref(), que recuperaría la referencia si aún sigue disponible.

Este ejemplo, a efectos clarificadores, crea una referencia débil hacia un objeto del DOM, permitiendo que sea eliminada cuando ya no sea necesario:


class Counter {
  constructor(domElement) {
    this.ref = new WeakRef(domElement)   // Referencia de forma débil el objeto
    this.start()
  }

  start() {
    this.count = 0
    this.timer = setInterval(tick, 1000)
  }

  stop() {
    clearInterval(this.timer)
    this.timer = 0
  }

  thick() {      
    const element = this.ref.deref() // Si aún existe la referencia, la recupera
      
    if (element) {
      element.textContent = ++this.count
    } else {
      this.stop()
      this.ref = null
    }
  }
}

const counter = new Counter(document.getElementById("counter"))
counter.start()

// Tras 5 segundos, eliminar el objeto del DOM supondrá liberarlo de la memoria
setTimeout(() => {
  document.getElementById("counter").remove()
}, 5000)

Aviso: ahondar en WeakRef supondría un capítulo aparte dada su complejidad y la imprevisibilidad de los escenarios a los que podría llevar. Su uso no está recomendado por lo que, si quieres conocer más allá del concepto tras este objeto, Google it.

Conclusión

Seguimos avanzando en las nuevas versiones del lenguaje que, desde el último gran release que supuso ECMAScript2015 / ES6, aportan solidez y practicidad al núcleo de esta criatura que es Javascript y que está petando la industria digital.

Si te interesa conocer más a fondo cómo evoluciona cada propuesta de integración para cada nueva versión anual, te invito a sumergirte en el repo de ECMA international, donde encontrarás:

¿Preguntas? Nos vemos en los comentarios 😚