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:
- Las propuestas ya integradas en cada una de las versiones del lenguaje, por año.
- Las propuestas en desarrollo activo, en curso pero no integradas aún, cuyo stage representa el nivel de evolución de cada una (siendo 4 la última fase previo a su release).
- Las propuestas retiradas por el comité debido a motivos de rechazo o abandono.
¿Preguntas? Nos vemos en los comentarios 😚