JavaScript — ES6 (Parte I)
Temario
- Introducción
- Parámetros de una función (predeterminados, resto de los parámetros)
- Operador rest/spread
- Literales de plantilla
- Propiedades de objeto (Property Shorthand, nombre de propiedades computadas (Computed Property Names), métodos)
- Desestructuración (Arreglos, Objetos, inicialización de variables, Desestructuración a profundidad, parámetros de una función)
i. Introducción
En la story[ref] pasada vimos lo que es ECMAScript, aprendimos que en JavaScript existen diferentes ediciones (ya sea que por que se han hecho mejoras o se han agregado nuevas cosas), es muy importante entenderlo, ya que JavaScript hizo bastantes cambios en la edición ES6 (ECMA2015), de hecho es muy común leer/escuchar a desarrolladores decir en “ES5 no se podía y en ES6 ya” o “en ES5 se hacía de esta forma ahora en ES6 es más fácil de esta otra forma”.
Así que veamos cuales son las mejoras/cambios que ha tenido ES6 (…mi idea es segmentarlo en varias stories).
En esta primera entrega veremos conceptos que nos van a servir para entender los siguientes temas, y conforme avancemos habrá una segunda parte (es probable que haga una tercera parte, pero viendo las otras ediciones) que nos va a ser muy útil para entender otros temas…
De hecho sin darte cuenta ya hemos manejado conceptos de ES6, por ejemplo scoping[ref] (variables de ámbito por bloque — Block-Scoped, Function-Scoped), así como las funciones de flecha [ref] (arrow function), los métodos find, findIndex[ref]… como puedes observar son cambios que nos han ayudado bastante a nuestro desarrollo y a amar ❤️ aún más a JavaScript :) .
Bueno ya basta de tanta introducción y vamos a lo interesante…
ii. Parámetros de una función
A lo largo de los años una de las grandes quejas como desarrolladores iba orientado a las funciones, ya que es donde se cometen muchos errores, o estas no eran tan potentes como en otros lenguajes; así que en ECMA2015 agregaron nuevas características, y una de ellas es precisamente el manejo extendido de parámetros de una función (Extended Parameter Handling), para entenderlo es necesario dividirlo en dos partes:
- Parámetros predeterminados
- Resto de los parámetros
Veamos cada uno de ellos…
a — Predeterminados
Sabemos que a una función le podemos mandar parámetros, veamos el siguiente ejemplo:
Hasta aquí no tenemos ningún problema, pero qué pasa si no le mandamos los 2 parámetros que espera sum, ya sea que solo enviemos 1 o ninguno:
Recordemos que cuando una variable sólo está declarada y NO inicializada su valor siempre va a ser undefined [ref], entonces cuando la función intenta hacer la suma de 1 + undefined nos va a retornar NaN [ref], y aquí es donde tenemos que hacer hacks o validaciones extras para los valores:
Podemos observar en nuestro ejemplo, que antes de realizar la suma, validamos si a o viene como undefined, en caso de que se cumpla, le asigne un valor predeterminado, si no mantenga el valor.
En la edición de ES6 se implementa la característica de establecer valores predeterminados, donde, cualquier valor predeterminado se considera opcional.}
Veamos el ejemplo anterior, aplicando lo que dice el enunciado de arriba:
Esto es genial, ya que no es necesario hacer alguna validación extraña o extra antes de realizar la operación.
Otro ejemplo:
Y lo mejor de todo es que no solo aplica a los valores tipo primitivos, ya que podemos establecer como parámetro predeterminado la ejecución de una función, veamos un ejemplo:
b — Resto de los parámetros
Imaginemos que tenemos que hacer una suma de “n” números, por lo tanto los parámetros pueden ser variables, la idea es de cualquiera de nuestros ejemplos que estan abajo funcionen:
Hay una forma de hacerlo; cuando nosotros creamos una función existe una variable local llamada arguments.
arguments es básicamente una variable local de tipo object que tienen CASI todas las funciones (excepto las arrow functions), y nos va a servir para obtener todos los parámetros que recibe una función.
Lo confuso de esta variable es que comporta como un array pero sin ser array (WTF!), expliquemos:
- Cuando imprimimos en consola la variable, pareciera ser un array
- Podemos acceder a los argumentos de manera independiente con un índice (comenzando con el 0), como si fuera un array.
- Podemos utilizar la propiedad length para obtener el ancho del “arreglo”, como si fuera un array.
- Pero si intentamos iterar este arreglo nos va a arrojar un error
Veamos un ejemplo:
En el ejemplo anterior es importante entender que al ser una variable local propia de una función NO SE DEBE declarar como parámetro, ya que de lo contrario es como si le estuviéramos asignando un nombre al primer parámetro, veamos el siguiente ejemplo:
Entonces hay que tener mucho cuidado de NO declararla como parámetro.
Ahora sí, continuando con nuestro ejemplo de sumar n números, podemos hacer uso de la variable local arguments para obtener los valores, entonces lo que vamos a hacer es convertir arguments en un arreglo iterable:
Vamos a explicar paso a paso la línea 4 de nuestro ejemplo:
- Array — Es el tipo de objeto que queremos [ref]
- prototype — Es la instancia que va a tener el arreglo[ref] (no olvidemos que toda la esencia de JS es prototípica)
- slice — Extrae un arreglo, al no tener parámetros, devuelve una copia completa [ref]
- call — Permite llamar una función desde un objeto, con el contexto del otro [ref][ref2]
Entonces lo que hacemos es un arreglo vacío (gracias a slice) y luego va a iterar el objeto y va a agregar los objetos al arreglo vacío, y al final lo retorna.
Entonces nuestro código completo quedaría de la siguiente manera:
Creo que esta forma es algo complicada de entenderla, además, estamos ocupando de manera directa prototype (no es recomendable usarlo[ref]); entonces en ECMA2015 decidieron crear el operador rest(…), veamos el siguiente ejemplo:
Veamos en el siguiente tema a detalle este operador…
iii. Operador rest/spread
El operador rest y el operador spread tienen en común utilizar la misma sintaxis ‘…’ (los tres puntos seguidos), por lo que tenemos dos tipos:
- Operador rest — Genera un array a partir de una lista de valores.
- Operador spread — Separa a una lista de valores de un array
a — Operador rest
El operador rest recoge múltiples elementos y los condensa en uno solo; veamos cómo quedaría nuestro ejemplo de la suma de n números:
Cuanto parámetro mandemos en la invocación de la función se va a estructurar como un array, siguiendo el ejemplo anterior numbers se va a convertir en un array de los parámetros, nosotros podemos mandar n variables:
La ventaja es que no es necesario ocuparlo de manera general, ya que podemos usarlo solo con ciertos parámetros, veamos el siguiente ejemplo:
En el ejemplo anterior estamos asumiendo dos parámetros obligatorios de entrada y el resto lo estamos asignando a un array.
Entonces cuando invocamos foo en la línea 4, sabemos que x = 1, y = 2, a = [6, 10, 15]; entonces el return queda de la siguiente manera:
( 1 + 2 ) * 3 = 9
Por lo que sí cumple la condición.
Para la invocación de foo de la línea 6, sabemos que x = 1, y = 2, a = [6, 10, 15, 22, 50]; entonces el return queda de la siguiente manera:
( 1 + 2 ) * 5 = 15
Por lo que sí cumple la condición.
Importante: El parámetro del operador rest debe ser el último parámetro.
b — Operador spread
El operador spread divide un array en elementos individuales, veamos un ejemplo:
Veamos un ejemplo más real, imaginemos que tenemos la función sum que recibe 3 parámetros (x, y, z), pero los valores a mandarle están dentro de un arreglo:
Con el operador spread podemos pasarlos de manera sencilla:
Inclusive podemos mezclar con otros parámetros:
Este operador también nos facilita bastante la manera de programar ya que con ES5 si nosotros queremos agregar elementos de un array a otro array, tenemos que usar una combinación de push, splice y concat, pero en ES6 con el operador de propagación spread resulta más sencillo y simple:
Basta con que lo pongamos nuestro array en la posición deseada del otro array y utilizar el operador spread, inclusive hacer un push (cuando queremos agregar valores al final de un array) es mucho más sencillo
Importante: Este operador solo funciona con iterables (arreglos) y NO con objetos.
Veamos más ejemplos de con los operadores rest y spread:
Con ES5:
Con ES6:
iv. Literales de plantilla
Una de las cosas más complicadas y odiada en JavaScript era combinar texto con variables, veamos el siguiente ejemplo y entenderás el por qué:
Y todavía se volvía mas complicado si queríamos saltos de línea:
Por fortuna en ES6 existe algo llamado plantillas de texto (Template literals or Template strings), estas son literales de texto que permiten combinar variables con texto en una o más líneas haciendo uso de las tildes invertidas ` `.
Podemos tener plantillas sencillas:
Inclusive si queremos combinar texto con variables solo es necesario saber la sintaxis, que es la siguiente:
Cada una de las variables que necesitemos basta envolverla en ${}, veamos el siguiente ejemplo:
Un ejemplo combinando texto, variables y salto de línea:
Como podemos observar el ejemplo anterior combinar variables con texto es más sencillo y limpio con ES6, pero eso no es todo!, dentro de ${expresión} al ser JavaScript, podemos hacer validaciones, sumas, etc.
v. Propiedades de objeto ( Enhanced Object Properties)
Esta es una de las características más útiles que tiene ES6 (a mi punto de vista); los literales de objeto nos ayuda a crear de manera más rápida los objetos; donde tenemos tres formas de hacerlo:
- Forma abreviada de inicializar propiedades (property shorthand)
- Nombre de propiedades computadas (Computed Property Names)
- Forma abreviada para escribir métodos (Method Properties)
a — Property Shorthand (Inicializar propiedades)
Cuando nosotros queremos inicializar un objeto en ES5 hacemos lo siguiente:
Podemos observar dentro del método person, la llaves (keys) de nuestro objeto tienen el mismo nombre que las variables, con ES6 hay una manera de simplificarlo:
Basta con que pongamos el nombre de la variable, internamente JavaScript verifica que si es una variable válida (exista), le asigna dicho nombre a la llave y a su vez asigna el valor correspondiente.
Es importante saber que si una variable no existe nos va arrojar un error
Veamos el siguiente ejemplo, donde, se ha agregado la variable otherProperty que no está declarada en ningún lado…
b — Métodos
Al momento de agregar una función dentro de un objeto, anteriormente se hacía de la siguiente manera:
Ahora con ES6 se hace de la siguiente manera:
¿Has visto el cambio? Así es… los ‘:’ (dos puntos) y el function ya no son necesarios para definir un método, por lo que en ES6 su sintaxis es más breve y limpia.
Importante la forma abreviada de las funciones deben tener un nombre (ej: foo: function(){}), por lo que en las funciones anónimas no va a funcionar ya que no tienen un identificador para referirse a ellas..
c — Nombre de propiedades computadas
Con ES5 cuando inicializamos un objeto y queríamos agregar llaves dinámicas, primero teníamos que inicializar el objeto y después al objeto con el operador [], agregamos la llave y su valor, veamos el siguiente ejemplo:
Con ES6, la sintaxis del inicializador de objetos acepta nombres de propiedades computadas, esto nos permite poner variables entre corchetes [], donde se computa y se utiliza como nombre de la propiedad, veamos el mismo ejemplo de arriba:
Inclusive podemos hacer uso de las literales de plantilla:
Otro ejemplo, aprovechando lo que hemos aprendido:
También podemos hacerlo con las funciones abreviadas:
vi. Desestructuración
El concepto de desestructuración no es nuevo, en otros lenguajes de programación (Python[ref], PHP[ref], etc) ya se implementa.
Podemos definirlo como la asignación de variables independientes de una estructura tipo objeto.
Como que no tiene mucho sentido el texto anterior, entonces, veamos con ejemplos lo que quiere decir:
a — Arreglos
Es muy común ver este tipo de código:
Antes si queríamos obtener los valores de un arreglo, lo mejor era con el nombre de la variable y el índice (var a = list[0];)…ahora con ES6 se hace por medio de la desestructuración:
Tomamos un conjunto de variables (array izquierdo) le asignamos un conjunto de valores (array derecho), además nos da la oportunidad de saltar valores:
Lo que está ocurriendo es lo siguiente:
- Posición 0 va a ser a
- Posición 1 no tendrá variable
- Posición 2 va a ser b
Podemos usar tanto let como const
Inclusive podemos hacer desestructuración anidada de arreglos:
Lo que está ocurriendo es lo siguiente:
- Pos 0 — a
- Pos 1 — b
- Pos2 — es un arreglo (internamente : Pos 0 — c; Pos 1 — no tendrá variable; Pos 2 — va a ser d )
- Pos 3 — no tendrá variable
- Pos 4— e
Importante: Hay que tener cuidado cuando asignamos variables (bloque izquierdo), ya que si no se encuentra en los valores (bloque derecho), lo que va a ocurrir es que las variables declaradas nos van a regresar undefined.
Veamos el siguiente ejemplo:
b — Objetos
Así como hemos hecho la desestructuración en arreglos, también podemos hacerlo con los objetos (objects).
Anteriormente era muy común hacerlo así:
Ahora con la edición de ES6 es mucho más sencillo:
Utilizamos un objeto literal (objeto izquierdo) le asignamos un conjunto de valores (objeto derecho).
Otra de las ventajas que tiene es que no necesitamos desestructurar todo el objeto, así como en array dejamos espacios ([a, , b]), con los objetos es más simple, ya que solo debemos declarar los que necesitamos:
Veamos otro ejemplo:
Importante: Hay que tener cuidado cuando asignamos variables (bloque izquierdo), ya que si no se encuentra en los valores (bloque derecho), lo que va a ocurrir es que las variables declaradas nos van a regresar undefined.
Veamos el siguiente ejemplo:
c — Inicialización de variables
Hasta aquí hemos hecho una desestructuración de manera simple.
Ahora imaginemos que dentro de una función recibimos una lista de valores, donde, siempre debemos recibir 3 números ([n1, n2, n3]); aunque algunas veces podemos recibir 2 (se da mucho el caso en la vida real); anteriormente se debía de hacer algo así (esta es una de las muchas posibles soluciones y la siguiente puede ser o no la mejor manera):
Con ES6 la desestructuración nos permite inicializar variables de una manera simple, veamos el mismo ejemplo:
No es necesario inicializar todos los valores, podemos hacerlo solo con algunos.
Lo mismo ocurre con los objetos, podemos inicializar valores; veamos el siguiente ejemplo:
Importante: La inicialización de variables solo ocurre cuando el valor es undefined, esto se menciona ya que los valores tipo falsy retorna su valor tal cual y no el de inicialización.
Veamos el siguiente ejemplo:
d — Desestructuración a profundidad
JavaScript nos permite hacer la desestructuración a profundidad, lo cual puede ser muy útil al momento de querer obtener ciertos valores; veamos el siguiente ejemplo:
Imaginemos que consumimos una API, donde nos regresa el siguiente objeto:
De todo ese objeto, para nosotros solo es útil su identificador (id), el título en español (title.spa), la imagen (backdrop), para obtener estos valores aplicando la desestructuración, quedaría de la siguiente manera:
Otra de las ventaja que tenemos es que podemos cambiarle el nombre a nuestras variables, usemos el ejemplo anterior:
Es tan sencillo como poner dos puntos “:” y el nuevo nombre de la variable, en nuestro ejemplo podemos observar que spa le hemos asignado el nombre de titleSpanish
Importante cuando asignamos un nombre a una variable ya no podemos hacer uso de su nombre original, para nuestro ejemplo ya no podemos usar spa, sólo titleSpanish
Podemos combinar objetos con arreglos y hacer que funcione de la misma manera:
Y sí… también podemos inicializarlos:
Otra ventaja que tiene la desestructuración, es que podemos invocar una función, y hacer la desestructuración de manera directa sin necesidad de asignarla a una variable… Genial ¿no?… (en el siguiente tema se explica)
Importante: Es necesario utilizar var, let o const para la desestructuración ya que de caso contrario nos arrojaría un error (hay una forma de evitarlo, pero como es una mala práctica no la pondré)
e — Parámetros de una función
Otra de las bondades que nos trajo ES6 es la desestructuración en los parámetros de una función, veamos el siguiente ejemplo:
Yo personalmente no recomiendo esta manera, ya que al momento de debuggear nuestra aplicación en producción es complicado detectar un error.
En la siguiente entrega vamos a ver JavaScript — Cómo funciona el Runtime Environment — JRE)
La entrega pasada vimos JavaScript — Historia, Estándar & Motores
Bibliografía y links que te puede interesar…