Lo nuevo en ECMAScript 2019
Del 29 al 31 de enero se llevó a cabo la reunión número 68 del comité TC-39, en donde se discutieron las propuestas que serán incluidas en la version 2019 final de ECMAScript.
Mathias Bynens, miembro del comité TC-39 anunció el listado de las propuestas que fueron aprobadas y llegaron al Stage 4 para ser incluidas en la siguiente versión:
🎉 New JavaScript features in ES2019:
— Mathias Bynens (@mathias) January 29, 2019
➡️ Array#{flat,flatMap}
➡️ Object.fromEntries
➡️ String#{trimStart,trimEnd}
➡️ Symbol#description
➡️ try { } catch {} // optional binding
➡️ JSON ⊂ ECMAScript
➡️ well-formed JSON.stringify
➡️ stable Array#sort
➡️ revised Function#toString
En este artículo exploraremos las más relevantes.
Tabla de contenidos
- Métodos Array.prototype.flat y flatMap para Arrays
- Object.fromEntries
- Métodos String.prototype.trimStart y trimEnd
- Symbol.prototype.description
- Catch con identificador opcional
- JSON superset de ECMAScript
- Revisión a Function.prototype.toString
Métodos Array.prototype.flat y flatMap para Arrays
Los métodos flat
y flatMap
, nos permiten aplanar arreglos, en otras palabras si tenemos un arreglo el cual posea elementos que sean sub-arreglos, estos nos permiten generar un arreglo nuevo, el cual tendrá todos los sub-elementos en el primer nivel.
Por ejemplo:
const array1 = [1, 2, [3, 4]];
const array2 = array1.flat();
// [1, 2, 3, 4]
El método flat
acepta un argumento, el cual es el nivel de profundidad. En el ejemplo anterior lo utilizamos sin especificarlo, por defecto la profundidad es 1
.
Si necesitamos un aplanamiento a mayor profundidad, podemos pasar un valor y esto hará que se aplanen recursivamente los arreglos:
const array1 = [1, 2, [3, [ 4, 5]]];
const array2 = array1.flat(2);
// [1, 2, 3, 4, 5]
Debemos notar también que método excluye huecos o elementos vacíos en el arreglo:
const array1 = [1, 2, , , 3, 4];
const array2 = array2.flat();
// [1, 2, 3, 4]
El método flatMap
nos permite combinar la funcionalidad de el método map
y el aplanado del método flat
. Normalmente map
nos permite generar un arreglo nuevo, basado en el resultado de la aplicación una función a cada elemento del arreglo original. Con flatMap
podemos retornar un sub-arreglo de cada elemento original, todos estos van a ser combinados en un solo arreglo al final.
Por ejemplo, imaginemos que tenemos un arreglo de “equipos”, cada equipo contiene una propiedad que es una lista de “jugadores”, si quisiéramos extraer a todos los jugadores de todos los equipos en un solo arreglo, lo podríamos hacer de la siguiente manera:
const jugadores = equipos.flatMap(e => e.jugadores)
La historia de esta propuesta es algo controversial, ya que inicialmente se había propuesto llamar al método Array.prototype.flatten
, y resulta que este nombre ya había sido utilizado hace muchos años por la librería MooTools.
MooTools fue una librería muy popular hace tiempo y algunos sitios continúan usándola, siempre se destacó por la característica de aumentar los prototipos de objetos nativos, - una práctica muy mala - como en este caso Array.prototype.flatten
.
Sin embargo, que MooTools implementara su propia version de flatten
en el prototipo de Array
no era realmente el problema, ya que asi como todos los demás métodos de Array.prototype
pueden ser sobre-escritos.
Array.prototype.flatten = /* implementación de MooTools */
El problema es que MooTools internamente utilizaba el ciclo for-in
en Array.prototype
y esperaba encontrar estos métodos como propiedades enumerables y resulta que por defecto todos las propiedades nativas de Array.prototype
definidas en la especificación como no enumerabes y el hecho de asignar un nuevo valor a ellas no las hace enumerables.
Finalmente, después de tanta discusión y bromas de cambiar el nombre del método a “smush”, todo esto se resuelve en la reunion de Mayo de 2018, en donde se decide renombrar flatten
por flat
.
Object.fromEntries
Este método se propone para ser la contraparte de Object.entries
, acepta un iterable de pares en forma [key, valor]
y retorna un objeto nuevo el cual contiene estas propiedades y valores correspondientes a los pares originales.
Por ejemplo:
const obj = {
a: 1,
b: 2,
c: 3,
}
const entries = Object.entries(obj)
// [ ['a',1], ['b',2], ['c', 3]]
const obj2 = Object.fromEntries(entries)
// { a: 1, b: 2, c: 3}
La combinación de Object.entries
y Object.fromEntries
nos permiten hacer cosas muy interesantes, como por ejemplo, filtrar las propiedades de un objeto estilo Array.prototype.filter
:
function filterObject(object, callback) {
const filtered = Object.entries(object)
.filter(([key, value]) => callback(key, value));
return Object.fromEntries(filtered);
}
const usuario = {
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "[email protected]",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
//...
},
"phone": "1-770-736-8031 x56442",
//....
}
filterObject(usuario, key => ['name', 'email', 'phone'].includes(key))
/*
{
name: "Leanne Graham",
email: "[email protected]",
phone: "1-770-736-8031 x56442"
}
*/
Y muchas cosas mas, podríamos replicar otras funciones de arreglos, por ejemplo Array.prototype.map
pero para objetos, invertir propiedades/valores, etc…
Métodos String.prototype.trimStart y trimEnd
Los métodos trimStar
y trimEnd
son simplemente complementarios al método trim
introducido en ECMAScript 2015 (ES6).
trimStar
simplemente elimina espacios en blanco al inicio de una cadena de caracteres:
const cadena = ' hola mundo'.trimStart()
// 'hola mundo'
Y trimEnd
elimina espacios en blanco al final de una cadena de caracteres:
const cadena = 'hola mundo '.trimEnd()
// 'hola mundo'
Es importante notar que dado que las cadenas de caracteres son tipos primitivos, por lo tanto son inmutables y estos métodos retornan siempre cadenas nuevas.
Symbol.prototype.description
Normalmente durante la creación de un símbolo se puede especificar una descripción para efectos de depuración, pero no había forma mas que llamando al método toString
para obtener esta descripción de vuelta, y el resultado incluía la palabra Symbol
y paréntesis, por ejemplo:
const prueba = Symbol('prueba')
prueba.toString()
// "Symbol(prueba)"
Esta propuesta permite exponer la descripción del símbolo por medio de la propiedad description
:
const prueba = Symbol('prueba')
prueba.description
// "prueba"
Catch con identificador opcional
Esta propuesta hace un cambio en la gramática de Javascript, permitiendo la omisión del identificador en el bloque de catch
, el cual era requerido por la sintaxis especifica de esta gramática.
Normalmente siempre teníamos que definir un identificador en el bloque catch
:
try {
/*...*/
} catch(error) {}
Ahora el bloque catch
ya no requerirá el identificador:
try { } catch {}
En mi experiencia, usualmente no es deseable ignorar errores, pero hay ciertos casos en los cuales es valido, por ejemplo:
- Cuando queremos probar alguna si alguna característica esta implementada:
let implementada;
try {
usarCaracteristicaX();
implementada = true;
} catch {
implementada = false;
}
Deseamos parsear una cadena de JSON potencialmente malformado:
let resultado = valorPorDefecto;
try {
resultado = JSON.parse(jsonPotencialmenteMalformado);
} catch {}
Acceder directamente a propiedades anidadas que podrían no existir, evitando chequear cada nivel:
//...
let calle = 'N/D'
try {
calle = data.usuario.direccion.calle
} catch {}
// ...
// En vez de:
const calle = data && data.usuario &&
data.usuario.direccion &&
data.usuario.direccion.calle
Relacionado:
JSON superset de ECMAScript
La propuesta JSON superset de ECMAScript agrega la capacidad a cadenas de texto literales, para contener dos caracteres que hasta la fecha no eran validos para Javascript, pero lo eran para el standard JSON (ECMA-404).
Los caracteres son:
- U+2028 LINE SEPARATOR
- U+2029 PARAGRAPH SEPARATOR
Para incluirlos en una cadena de caracteres, teníamos que hacerlo por medio de una secuencia de escape:
const cadena = '\u2028'
cadena.charAt(0) // "\u2028"
La inclusión del carácter real en una cadena producía un error sintáctico:
const evaluarCadena = '"\u2028"'
eval(evaluarCadena)
// SyntaxError: "" string literal contains an unescaped line break
Dado que el estándar de JSON ya incluía estos caracteres, se decidió quitar esta limitación en la gramática de literales de cadenas de caracteres.
Revisión a Function.prototype.toString
En esta propuesta, se hace una revisión al método Function.prototype.toString
, entre los puntos mas relevantes, se encuentran:
- Funciones definidas en Javascript deberán producir exactamente el código fuente original, incluyendo espacios en blanco y comentarios:
function /* comentario */ prueba () { /* otro comentario */ }
// Antes:
prueba.toString();
// "function prueba() {}"
// Ahora:
foo.toString();
// "function /* comentario */ prueba () { /* otro comentario */ }"
- Estandarizar la representación en cadena de caracteres de funciones built-in y de objetos del entorno:
Object.create.toString()
// "function create() { [native code] }"
alert.toString()
// "function alert() { [native code] }"