Desde 2015 con la publicación del estándar ES6, Javascript ha recibido actualizaciones a un ritmo vertiginoso. ¿El resultado? JS es ahora uno de los lenguajes de programación más modernos y versátiles.

Las prácticas arrow functions, las técnicas de destructuring, o los operadores async / await son algunas de las novedades que se incorporaron hasta 2017.

Javascript ES2018

En este artículo voy a detallarte las novedades más interesantes de Javascript, incorporadas en 2018.

1 – Asignación por destructuring en Objetos

ES6 incorporaba el spread operator y los parámetros rest para hacer asignaciones por destructuring con Arrays.

Esto permitía 2 cosas:

  • Asignar un subarray de elementos a una única variable
  • Y descomponer un array en valores individuales.

    Funcionaba así:

// Rest elements for array destructuring assignment:
const primes = [2, 3, 5, 7, 11];
const [first, second, ...rest] = primes;
console.log(first); // 2
console.log(second); // 3
console.log(rest); // [5, 7, 11]

// Spread elements for array literals:
const primes2 = [first, second, ...rest];
console.log(primes2); // [2, 3, 5, 7, 11]

Pues bien, desde 2018, se pueden aplicar los mismos operadores de forma análoga a los objetos. Así:

// Rest properties for object destructuring assignment:
const person = {
    firstName: 'Peter',
    lastName: 'Parker',
    age: 26,
} 
const { age, ...name } = person;
console.log(age); // 26
console.log(name); // {firstName: 'Peter', lastName: 'Parker'}

// Spread properties for object literals:
const person2 = { age, ...name };
console.log(person2);
// {age: 26, firstName: "Peter", lastName: "Parker"}

¿Y de qué sirve esto?
Aquí tienes algunas situaciones en las que te va a ser útil:

  • Clonación de objetos:
    Es una forma bastante más simple de clonar objetos planos que su alternativa (Object.assign({})).
    Ejemplo:
// Shallow-clone an object:
const data = { x: 42, y: 27, label: 'Treasure' };
// Antes:
const clone1 = Object.assign({}, data);
// Ahora:
const clone2 = { ...data };
  • Merge de objetos:
    Del mismo modo, te facilita la fusión entre 2 objetos
    Ejemplo:
// Merge two objects:
const defaultSettings = { logWarnings: false, logErrors: false };
const userSettings = { logErrors: true };

// Antes
const settings1 = Object.assign({}, defaultSettings, userSettings);

// Ahora
const settings2 = { ...defaultSettings, ...userSettings };

// En ambos casos el resultado es
// { logWarnings: false, logErrors: true }
  • Deshacerte de parte de un objeto:
    A veces necesitas una versión «reducida» de un objeto, porque no te interesa exponer alguna propiedad.

Esto es extremadamente simple ahora:

const person = {
    name: 'Peter',
    age: 26,
    gender: 'Male',
    email: 'private@email.com',
    address: 'private address 25, 08180, Somewhere.'
} 
const { email, address, ...simplePerson } = person;

console.log(simplePerson); //{name: "Peter", age: 26, gender: "Male"}

2 – finally en Promises

Otra incorporación de ES6 fueron las Promises: Un mecanismo para ejecutar tareas asíncronas, y llamar una función de callback en caso de éxito (resolve) o error (reject).

Pues bien, desde 2018 las Promises te permiten llamar a una función de callback al completarse (independientemente del resultado). A continuación te dejo un ejemplo claro de uso:

const fetchAndDisplay = ({ url, element }) => {
  showLoadingSpinner();
  fetch(url)
    .then((response) => response.text())
    .then((text) => {
       element.textContent = text;
    })
    .catch((error) => {
       element.textContent = error.message;    
    })
    .finally(() => {
       hideLoadingSpinner();
    });
};

3 – Iteradores asíncronos

Un iterador es un contenedor de elementos que se puede recorrer sin conocer su arquitectura, solo llamando a su método next().

En Javascript, existían los iteradores síncronos, donde este método devolvía dos valores:

  • value con el elemento actual del contenedor
  • y done indicando si el iterador ha finalizado o no.

Supongamos un iterador que contiene el array [1,2,3]. Obtendríamos sus valores así:

syncIterator.next();
//{value:1, done:false} 
syncIterator.next();
//{value:2, done:false} 
syncIterator.next();
//{value:3, done:false} 
syncIterator.next();
//{value:undefined, done:true} 

Además, puedes usar for..of para iterarlo (devuelve directamente el valor de cada iteración y finaliza internamente cuando done es true):

for (let value of syncIterator) {
    console.log(value);
} // output: 1, 2, 3

Desde 2018, Javascript acepta también iteradores que devuelven valores asíncronos. Cada llamada a next, en este caso, lo que devuelve es una Promise, así que para capturar el siguiente valor de la iteración, lo harías así:

asyncIterator.next().then(({ value, done }) => /* ...do something with value and done... */);

Buclando sobre Iteradores asíncronos

Junto con los iteradores asíncronos, se ha incorporado también la sintaxis for-await-of para recorrerlos.
Imagina un iterador que lee un archivo (de forma asíncrona como es habitual) línea a línea. Podríamos recorrer y printar todas las líneas, así:

for await (const line of readLines(filePath)) {
  console.log(line);
}

4 – Mejoras en las expresiones regulares

Se han añadido varias mejoras al sistema de expresiones regulares de Javascript. Entre otras cosas…

Named capture groups

Ahora se pueden identificar grupos de captura a través de nombres, lo que facilita su lectura. Por ejemplo:

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

Lookbehind assertions

Hasta el momento, las expresiones regulares de JS tenían un mecanismo para comprobar si una expresión iba seguida de un determinado patrón (lookahead: ?=pattern).

Por ejemplo, la siguiente expresión : /^([0-9]{2})(?=H)/ daría positivo para números de 2 cifras seguidos de la letra H (sin incluirla en el match).

const regex = /^([0-9]{2})(?=H)/;
regex.exec('12H');
// encuentra 12 como match.

Lo que no había es un mecanismo para comprobar si la expresión estaba precedida de un determinado patrón. Esto es lo que llamamos lookbehind (?<=pattern). Para comprobar si una cantidad monetaria esta precedida por el símbolo del €, podríamos hacerlo así:

const regex = /(?<=€)([0-9])+/;
regex.test('€2000');
// encuentra 2000 como coincidencia

Reflexiones personales

Como ves, JS no para de evolucionar. Cada año se incorporan novedades para facilitar el trabajo de los desarrolladores y adaptar el lenguaje a las necesidades que se detectan. En próximos posts te contaré las novedades que se han añadido este año 2019, y lo que se espera para 2020.

¿Te ha gustado este artículo? No te cortes, déjame un comentario y ayúdame a compartirlo 😉