JavaScript — Map, Set, Proxy, Reflect & Export modules ES6 (Parte IV)
Temario
- Introducción
- Estructura de datos (Map, Set, WeakSet, WeakMap)
- Proxy (get, set, has, deleteProperty, ownKeys)
- Reflect (get, set, construct)
- Exportar e importar módulos
i. Introducción
Vamos por la cuarta parte de ES6 (parte I[ref], parte II[ref], parte III[ref]) en la entrega pasada vimos los Symbols, así como los iteradores (Symbol.iterator), y por último explicamos cómo funcionan las funciones generadores
En esta cuarta entrega vamos a ver mas conceptos interesantes, y más cosas que se han agregado, algunos temas son una mezcla de todas las entregas pasadas …
ii. Estructura de datos
a — Map
En JavaScript estamos acostumbrados a crear una colección de datos por medio de objetos, formado por propiedad/valor
Sabemos que la propiedad solo puede ser un number, string ó Symbol; debemos tener cuidado, ya que si ponemos una propiedad numérica (1) o una cadena (“1”) el objeto realmente no distingue, por lo que los encima y toma el último agregado:
Otra de las desventajas de usar objetos, es que cuando la propiedad es tipo numérico, el orden de elementos no los conserva simplemente los ordena:
Tomando en cuenta los detalles de arriba, es como surge Map…
El objeto Map almacena pares clave/valor. Cualquier valor (tanto objetos como valores primitivos) pueden ser usados como clave o valor[ref]
La sintaxis es la siguiente:
Veamos un ejemplo:
Como la definición nos dice:
Cualquier valor pueden ser usados como clave o valor
Esto quiere decir que SÍ distingue entre un valor numérico, string, boolean, hasta objeto, y es posible gracias al algoritmo SameValueZero[ref], que su comportamiento es muy parecido a la igualdad estricta (===).
Map tiene varios métodos y propiedades que nos van a ayudar a agregar, modificar o eliminar datos de nuestra colección, así como obtener información; veamos cuales son y cómo funcionan:
- map.set(key, value) — Es útil cuando queremos agregar nuevos datos a nuestra colección, si seteamos una variable que ya existe, lo único que va a hacer es sobreescribir la propiedad.
También podemos encadenar el método .set, para agregar más de un valor; ya que Map retorna a sí mismo, y esto nos permite encadenar las llamadas
- map.get(key) — Es útil cuando queremos obtener el valor de nuestra colección de datos, en caso de no encontrar la clave, nos regresa undefined de valor (recordemos que distingue los tipos de datos en la clave).
- map.has(key) — Es útil cuando queremos validar si existe la clave en nuestra colección de datos (Si existe nos regresa true, caso contrario false)
- map.delete(key) — Es útil cuando queremos eliminar un valor por medio de una clave, dentro de nuestra colección de datos (Si existe y lo ha borrado nos regresa true, caso contrario false)
- map.clear() — Es útil cuando queremos eliminar toda la colección de datos
- map.size — Es útil cuando queremos saber cuántos elementos tiene nuestra colección de datos
Teniendo en cuenta los métodos que tiene, veamos un ejemplo, donde usamos una clave tipo objeto:
Hay que tener en cuenta que el objeto que usemos como clave, SIEMPRE va a ser una referencia al original (excepto si lo clonamos), como podemos observar en nuestro ejemplo de arriba:
- En la línea 1, creamos nuestro objeto person
- En la línea 5, asignamos de clave nuestro objeto person
- En la línea 9, obtenemos el valor por medio de la clave person
- En la línea 11, agregamos un nuevo valor a nuestro objeto person
- En la línea 13, obtenemos el valor por medio de la clave person(sin importar que le hemos manipulado nuestro objeto, si devuelve el dato)
Hasta aquí todo bien, pero… ¿para iterar los datos?, podemos hacerlo de dos maneras: for…of o con forEach
- for…of — Nos retorna un arreglo [key, property]
- forEach — Es un método incorporado, similar al de un arreglo (Array), donde nos retorna 3 propiedades key, property, map.
Además tiene 3 métodos para obtener datos:
- map.keys() — Es útil para obtener todas las claves de nuestro map.
- map.values() — Es útil para obtener todos los valores de nuestro map.
- map.entries() — Es útil para obtener un iterable [key, property], que es el mismo que usa el for..of
Como vemos, es más limpio y ordenado utilizar Map que Object, pero, eso no significa que ya siempre debamos usar Map y olvidarnos de Object, siempre es bueno hacer una evaluación y ver cual conviene…
Cuando puede ser ideal usar Object:
- Sabemos que su manejo es más simple, por lo que es útil cuando sabemos que todas sus claves son cadenas, enteros o Symbol; ya que si queremos acceder a un dato por medio de una clave es más sencillo y rápido.
- Cuando aplicamos lógica a una propiedad (una función), y dentro de ella ocupamos propiedades de nuestro objeto(this), ya que con Map no es posible hacerlo.
- Object tiene soporte con JSON (y sus métodos), mientras que Map no (por ahora), por lo que sí tenemos que trabajar con los métodos de JSON es mejor Object.
Cuando puede ser ideal usar Map:
- Cuando necesitamos agregar y eliminar valores, es mejor hacerlo con Map, ya que como sabemos con Object nos llega a causar diversos problemas, entre ellos de rendimiento.
- Una gran ventaja de Map, es que conserva el orden de las claves, por lo que si el orden que nosotros los guardamos es importante, Object no nos va a funcionar.
- Map nos garantiza un buen rendimiento en la iteración de los datos.
- Cuando las claves son desconocidas hasta el momento de ejecución (quizás por que vengan de una API, o estén cambiando constantemente), sin duda alguna Map es nuestra elección.
b — Set
El objeto Set te permite almacenar conjunto de valores únicos de cualquier tipo, incluso valores primitivos u objetos de referencia.[ref]
Este objeto es muy útil cuando queremos guardar valores (sin claves) en un arreglo, donde cada uno de los valores solo pueda estar una vez.
La sintaxis es la siguiente:
Veamos un ejemplo:
Como podemos observar en nuestro ejemplo de arriba, se han puesto varios valores repetidos, pero al momento de obtener los valores, solo nos ha retornado una lista de valores únicos.
Set tiene varios métodos y propiedades que nos van a ayudar a agregar, modificar o eliminar datos de nuestra colección, así como obtener información; veamos cuales son y cómo funcionan:
- set.add(value) — Es útil para agregar un valor, nos retorna conjunto de datos actualizado.
Cuando agregamos un valor que ya estaba en nuestro conjunto de datos, no va a hacer nada.
- set.delete(value) — Es útil para eliminar un valor, devuelve true si ha eliminado el valor, de lo contrario false
- set.has(value) — Es útil para validar si existe el valor en el conjunto de datos (Si existe nos regresa true, caso contrario false)
- set.clear() — Es útil para eliminar todo el conjunto de datos
- set.size — Es útil para saber cuántos elementos tiene nuestro conjunto de datos
Hasta aquí todo bien, pero… ¿para iterar los datos?, podemos hacerlo de dos maneras: for…of o con forEach
- for…of — Nos retorna un arreglo [value]
- forEach — Es un método incorporado, similar al de un arreglo (Array), donde nos retorna 3 propiedades value, value, set. No hay claves, por lo que regresa dos veces el valor, y esto tiene la culpa el Map, ya que ambos comparten forEach, y recordemos que regresa key, value, Map.
Al ser compatible con Map, cuenta con los mismos 3 métodos para obtener datos:
- set.keys() — Es útil para obtener todas las claves (como no tiene claves, nos regresa los valores) de nuestro set.
- set.values() — Es útil para obtener todos los valores de nuestro map.
- set.entries() — Es útil para obtener un iterable [value, value], que es el mismo que usa el for..of(como no tiene claves, nos regresa los value, value).
Es importante no confundir Set con Array, ya que son conceptos totalmente diferentes.
- Sabemos que Array es un bloque de datos asignados en memoria de manera consecutiva asignando en un índice (key), mientras que Set es un conjunto de datos sin la necesidad de ser asignados a un índice(key).
- En un Array podemos tener elementos duplicados a diferencia de Set.
c — WeakMap
¿Recuerdas cuando vimos cómo funciona el JRE [ref]?, sobre todo la parte de Heap:
… el recolector de basura (garbage collection)[ref] los elimina de manera automática.
En un objeto de Map, los elementos NUNCA se recogen basura, mientras que con WeakMap permite que todos sus elementos se recojan libremente.
Su sintaxis es la siguiente:
Antes de ver un ejemplo, es importante saber que cada clave que tenga WeakMap, debe ser un objeto, ya que cuando se pierde la referencia, es cuando el valor para por la recolección de basura.
WeakMap tiene varios métodos que nos van a ayudar a agregar, modificar o eliminar datos de nuestra colección, así como obtener información; veamos cuales son y cómo funcionan:
- weakMap.get(key) — Es útil para obtener el valor.
- weakMap.set(key, value) — Es útil para modificar o agregar un valor.
- weakMap.delete(key) — Es útil para eliminar el valor
- weakMap.has(key) — Saber si existe (si existe regresa true, caso contrario false)
Veamos un ejemplo completo de todos los métodos:
Es importante a tener en cuenta:
- Con WeakMap, NO se puede iterar sobre las claves o valores o valores-clave.
- NO tiene un método para borrar TODOS los elementos (se debe borrar uno a uno con el método delete o asignando a la clave null).
- NO podemos verificar el tamaño.
Todas estas limitaciones, se deben al recolector de basura, ya que como bien comentamos arriba JavaScript se encarga de limpiar en memoria de manera automática; por lo que no va a ser posible saber cuántos elementos nos quedan, o poder iterar.
Entonces… ¿cuándo es útil usarlo? bueno, tenemos varios casos donde puede ser útil.
Ejemplo 1: Imaginemos que tenemos una librería de terceros y queremos persistir datos (quizás un usuario o un token) mientras este exista, al momento de que se elimine los valores se pierdan; con esto nos genera “cierta protección” a esos datos sensibles que no queremos se vean…
En el ejemplo anterior estamos borrando de forma manual el objeto d3 = null, y al intentar obtener el valor en el wMap, nos regresa undefined, ya que fue no existe.
Ejemplo 2: Imaginemos que tenemos un objeto DOM del HTML (quizás un div) y en este vamos a guardar el data que está mostrando y mientras este exista, vamos a poder acceder a sus datos, pero el momento que se elimine no haya forma de obtener los datos.
Primero en la tab de console que podemos observar:
Cuando le damos click al botón en consola podemos observar:
d — WeakSet
El objeto WeakSet se comporta de manera similar a WeakMap (recolección de basura), pero con las reglas y características de Set:
Su sintaxis es la siguiente:
Antes de ver un ejemplo, es importante saber que cada clave que tenga WeakSet, debe ser un objeto, ya que cuando se pierde la referencia, es el mismo objeto que se va a la recolección de basura.
WeakSet tiene varios métodos que nos van a ayudar a agregar, modificar o eliminar, así como obtener información; veamos cuales son y cómo funcionan:
- weakSet.add(value) — Es útil para agregar un objeto.
- weakSet.delete(value) — Es útil para eliminar el valor
- weakSet.has(value) — Saber si existe (si existe regresa true, caso contrario false)
Veamos un ejemplo completo de todos los métodos:
Podemos observar que de la línea 8 a la 10, agregamos el mismo objeto, pero cuando imprimimos wSet en la línea 12, sólo nos trae 2 objetos (recordemos que Set permite agregar valores únicos, lo mismo aplica para WeakSet.
Es importante a tener en cuenta:
- Con WeakSet, NO se puede iterar sobre las claves o valores o valores-clave.
- NO tiene un método para borrar TODOS los elementos (se debe borrar uno a uno con el método delete o asignando a la clave null).
- NO podemos verificar el tamaño.
- Solo podemos agregar objetos (no primitivos).
- El objeto va a existir siempre y cuando exista una referencia.
iii. Proxy
El objeto Proxy se usa para definir un comportamiento personalizado para operaciones fundamentales (por ejemplo: para observar propiedades, cuando se asignan, invocación de funciones, etc).[ref]
En palabras más simples, con el objeto Proxy vamos a poder interceptar cualquier acceso a los atributos y métodos (inclusive si no existen) para hacer cambios o validaciones.
Su sintaxis es la siguiente:
Ejemplo: Imaginemos que tenemos objeto (menú de comida), y queremos obtener algunos datos, entonces…
En el código anterior podemos observar que se comporta de manera natural, menu.entrees y menu.dessert nos regresan una lista de comida, mientras que menu.breakfast nos retorna undefined (ya que no está dentro de nuestro objeto).
Ahora, lo que queremos es observar en todo momento nuestro objeto menu, pero sin hacer ningún cambio, esto quiere decir que no vamos a tener ninguna función (trampa), veamos el siguiente ejemplo:
En nuestro ejemplo, lo único que ha cambiado es la línea 8, lo que hicimos fue decirle que el objeto Proxy sea el que esté observando todo lo que sucede con nuestra variable menu, lo interesante de este caso, es que se sigue comportando de la misma manera que nuestro primer ejemplo.
Cuando vamos a un restaurante, no vamos directo a la cocina y hacemos nuestro pedido, simplemente nos sentamos y le decimos al mesero que vamos a querer, el se encarga de tomar nota de todo lo que pedimos, y si hay algo que falta o que no esté, simplemente nos lo comunica; en caso de que no haya problema con nuestro pedido, él se lo pide al chef; y cuando está listo, el mismo mesero lo recogerá y lo llevará a nuestra mesa.
Algo parecido sucede con el Proxy, cuando nosotros intentamos acceder a cualquier objeto envuelto en un Proxy, este va a ser el encargado de verificar que todo esté correcto y bien, en caso de que no haya problema, va a realizar la tarea, de lo contrario nos lo comunica.
En el handler vamos a poner funciones[ref][ref] (“trampas”), que van a ser las encargadas de interceptar cualquier acceso al objeto…
a — get
Su método interno es [[Get]], el método que nosotros vamos a utilizar es get y este se va a invocar cuando leamos una propiedad y además va a recibir 2 parámetros (realmente 3, por ahora solo veremos 2):
- target — Objeto que estamos interceptando.
- propKey — Nombre de la propiedad que queremos acceder.
Ejemplo 1.2: En nuestro ejemplo anterior, tenemos un problema , cuando queremos obtener una propiedad que no se encuentra, nos retorna undefined, estaría genial, si hubiera alguna forma de decirle al desarrollador o a la aplicación que esa propiedad no existe (quizás mediante un mensaje).
Imaginemos que el cliente nos puede preguntar cuáles son las entradas, comidas, agua o postres que tenemos, entonces nosotros vamos a crear una función (handler), donde agregaremos el método get (trampa que se va a activar cuando pidan algún dato), donde, vamos a validar lo que nos piden sea válido, ya que puede ser que ande despistado y nos pida algo que no tenemos.
Como podemos observar, hemos hecho una simple validación, de que si existe, nos regrese los datos, caso contrario nos regrese un mensaje “Sorry :(“
Veamos el ejemplo completo:
Podemos observar en las líneas 17 y 18, que nos regresa esa parte del menú si existen, caso contrario nos retorna el mensaje ‘Sorry :(’.
Ejemplo 2: Tenemos una aplicación donde tenemos un traductor, dependiendo del idioma, y la palabra/frase que le mandemos, será el resultado que nos va a regresar, pero en caso de que no exista dentro de nuestro catálogo, debemos retornar la palabra/frase que nos mandó, y así, es mejor que se vea algo no traducido, a un error o undefined; entonces, tenemos la siguiente lista:
Vamos a hacer algunos cambios a nuestro Proxy, crearemos una closure[ref], donde primero le mandamos nuestro catálogo de idiomas, después, pasamos el idioma, para que el Proxy observe mediante la función get, y verificar que si existe el idioma y la palabra/frase, nos regrese la cadena, caso contrario nos retorna lo que le enviamos…
Veamos el ejemplo completo:
b — set
Su método interno es [[Set]], el método que nosotros vamos a utilizar es set y este va a modificar/agregar una propiedad y además va a recibir 3 parámetros (realmente 4, por ahora solo veremos 3):
- target — Objeto que estamos interceptando.
- propKey — Nombre de la propiedad que queremos acceder.
- value — El valor de la propiedad
Importante: Dentro del método debemos retornar true si se agregó/modificó correctamente, caso contrario false.
Ejemplo 1.3: Imaginemos que nos habla el chef y nos pide que agreguemos un nuevo platillo y además quiere agregar desayunos, entonces, el código quedaría algo así:
De la línea 6 a la 11, lo que hacemos es validar si existe la propiedad (entrees, dish, etc), si la sección existe, entonces agregamos el platillo, caso contrario, agrega una nueva sección y además (si es que tiene) agrega los platillos…
Veamos el ejemplo completo:
Ejemplo 3: Imaginemos que tenemos tenemos los datos de una persona, donde queremos validar que la edad sea solo numérica, y además sea mayor a 18 y menor a 100.
c — has
Su método interno es [[HasProperty]], el método que nosotros vamos a utilizar es has y este va a validar una propiedad y además va a recibir 2 parámetros:
- target — Objeto que estamos interceptando.
- value — El valor de la propiedad.
Ejemplo 1.4: Imaginemos que el cliente quiere saber si tenemos un platillo específico:
En el ejemplo de arriba, espera el menú principal (desayuno, comida, cena, entradas, etc), un divisor => (puede ser el que uds. quieran), y el platillo que están buscando, entonces:
- En la línea 1, queremos saber si dentro del menú, en los postres tienen pastel
- En la línea 2, queremos saber si dentro del menú, en el desayuno tienen huevos con jamón
- En la línea 3, queremos saber si dentro del menú, en las aguas, tiene de limón
Nuestro wrapper, quedaría de la siguiente manera:
Nota: La forma en que se resolvió, solo sirve de ejemplo, la solución, quizás, puede que no sea la mejor o la mas optima.
Veamos el ejemplo completo:
Ejemplo 4: Imaginemos que tenemos el perfil de un camión (modelo, año, altura, color, peso, etc), y nosotros hemos construido un puente, por lo que necesitamos saber si ese camión va a poder pasar debajo de él, entonces, nuestro modelo es el siguiente:
Vamos a generar un proxy, donde validamos si el camión está dentro del rango:
Veamos el ejemplo completo:
Una ventaja de usar .has es que podemos hacerle sobrecarga al operador in, como ya hemos mencionado, se va a encargar de validar si un valor está dentro de un objeto, imaginemos que queremos validar si un número se encuentra dentro del rango, pero sin tener algún objeto de por medio, entonces, nuestro ejemplo quedaría de la siguiente manera:
Además del operador in, también podemos sobrecargar delete y new
d — deleteProperty
Su método interno es [[Delete]], el método que nosotros vamos a utilizar es deleteProperty se ejecuta cuando eliminamos una propiedad y además va a recibir 2 parámetros:
- target — Objeto que estamos interceptando.
- propKey — Nombre de la propiedad que queremos acceder.
Importante: Dentro del método debemos retornar true si se eliminó correctamente, caso contrario false.
Ejemplo 1.5: El restaurante ha llegado a un acuerdo con el chef, garantizando que siempre va a tener entradas y aguas de sabor sin importar la hora, por lo que en el sistema siempre deben aparecer estos dos, la idea es de que nosotros avisemos que esos dos no se pueden borrar, veamos el ejemplo:
e — ownKeys
Su método interno es [[OwnPropertyKeys]], el método que nosotros vamos a utilizar es ownKeys se ejecuta cuando utilizamos: Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in, Object/keys/values/entries, y además va a recibir 1 parámetro:
- target — Objeto que estamos interceptando.
Cuando iteramos un objeto con el for…in obtenemos las claves con Object.keys, utilizan un método interno OwnPropertyKeys, donde nosotros podemos interceptar con el método ownKeys.
Ejemplo 5: Imagina que tenemos una página donde mostramos información de los artistas del momento, donde, enseñamos información básica a usuarios gratuitos, e información completa a usuarios de paga.
Nota: Solo es un ejemplo, obviamente no deberíamos de estar haciendo este tipo de validaciones del lado de front.
Entonces cuando nosotros consumimos la API, nos llega un JSON así:
Las propiedades que inicien con guión bajo, no deben ser visibles para los usuarios gratuitos.
Veamos el ejemplo completo:
Cuando la bandera VIP es false, nos regresa en consola lo siguiente (solo las propiedades que NO tengan guión bajo):
Ahora, cuando cambiamos la bandera VIP a true, el resultado cambia (nos regresa todas las propiedades):
Si quieres ver todas las funciones “trampa”, revisa la documentación oficial[ref].
iv. Reflect
Reflect es un objeto integrado que proporciona métodos para operaciones JavaScript interceptables. Los métodos son los mismos que los de los controladores proxy[ref] .
Reflect es un Objeto global (como JSON, Math), que nos va a permitir “mirar en el interior” (instrospección) de los métodos internos que tiene JavaScript, pero de manera limpia y con retornos de mayor valor.
Al ser un objeto global, Reflect no es un constructor, esto quiere decir que no puede ser usado con el operador new o invocarlo como una función, por lo que todos sus métodos serán estáticos.
Lo interesante es de que el concepto no es nuevo en JavaScript, actualmente contamos con algunas reflexiones dentro de Object como keys, getOwnPropertyNames, entre otros; y no solo aplica a Object, también a las funciones como apply, call…
— Operadores de palabras clave como ciudadanos de primera clase
Los métodos de reflexión, nos da la oportunidad de llamar a operadores como new y delete como si fueran funciones (Reflect.construct y Reflect.deleteProperty).
Ademas, para cada método interno atrapable por Proxy, hay un método para Reflect, con el mismo nombre y argumentos de la trampa de Proxy. Es muy útil cuando queremos reenviar la llamada al objeto con los mismos argumentos, es decir: Reflect.<method>
a — Reflect.get
Lo utilizamos para establecer un valor de la propiedad de un objeto,
Su método interno es [[Get]], el método que nosotros vamos a utilizar es set se utiliza para retornar el valor de una propiedad de un objeto, donde, espera 3 parámetros:
- target — Objeto que estamos interceptando.
- propKey — Nombre de la propiedad que queremos acceder.
- receiver — Objeto donde va a tomar su this, por default toma el del target
Ejemplo 1:
Veamos el mismo ejemplo, pero con Proxy:
Nuestro ejemplo es tan transparente que pareciera que no estamos usando Reflect y Proxy.
Ejemplo 2:
Hasta aquí parece que todo está bien, ahora cómo podemos extender todas las propiedades y métodos que tiene el objeto user… para eso vamos a utilizar el tercer parámetro (receiver), veamos el ejemplo:
Ejemplo 3:
Imaginemos que tenemos un objeto con que tiene de propiedades num1, num2, y el método sum, la idea es poder aprovechar ese objeto, mandandole diferentes objetos…
Entonces:
- Reflect.get — Lee una propiedad de objeto, y además nos da la oportunidad de cambiar el contexto del this de manera rápida y limpia.
b — Reflect.set
Lo utilizamos para establecer un valor de la propiedad de un objeto.
Su método interno es [[Set]], el método que nosotros vamos a utilizar es set se utiliza para establecer un valor de la propiedad de un objeto, donde, espera 3 parámetros:
- target — Objeto que estamos interceptando.
- propKey — Nombre de la propiedad que queremos acceder.
- value — El valor de la propiedad
Nos va a devolver un true si el valor de la propiedad se estableció con éxito, caso contrario false.
Ejemplo 1:
Veamos el mismo ejemplo, pero con Proxy:
Nuestro ejemplo es tan transparente que pareciera que no estamos usando Reflect y Proxy.
Ejemplo 2:
Y de la misma manera que get, podemos usar el parámetro extra receiver.
Entonces:
- Reflect.set — Escribe una propiedad de objeto y va a devolver true/false, y además no da la oportunidad de cambiar el contexto del this de manera rápida y limpia.
Si quieren profundizar más del tema de Reflect, les recomiendo las siguientes ligas[ref][ref][ref][ref][ref].
v. Exportar e importar módulos
Esta es otra de las grandes mejoras que tiene ES6, poder importar/exportar módulos (código de JavaScript en JavaScript).
En algunas aplicaciones web (hasta la fecha), se tiene el problema de dependencia con la carga de archivos:
- Scripts que no se cargaron
- No fueron cargados en el orden correcto
- No pueden leer los nodos del DOM
- Mover la carga de los archivos (antes del cierre del body)
- Carga de todos los archivos JS en el index.html, lo que es igual a mal rendimiento y mantenibilidad, ya que se deben de cargar siempre TODOS desde el principio, con el riesgo de no utilizar más de la mitad.
La idea es poder separar esos archivos de manera lógica y que solo sean llamados cuando se necesiten, y así es como nació la idea export/import; y la verdad los de JavaScript se han lucido, ya podemos exportar una variable (cualquier tipo de dato), clase, función; esto es genial, ya que de un código completo podemos exportar sólo lo necesario (este es el verdadero poder de importar/exportar módulos).
Antes de empezar a crear componentes o módulos reutilizables, tengamos en cuenta lo siguiente:
- Si se necesita más de dos veces, es bueno crear un módulo o componente.
- Debe poder ser reutilizable en otras aplicaciones web.
- El código del módulo se ejecuta automáticamente en modo estricto.
- Las variables creadas en el nivel superior del módulo, no se agregan automáticamente en el ámbito global compartido, esto quiere decir que solo existen dentro del alcance del nivel superior del módulo.
- Los módulos pueden importar otros módulos.
a — Exportar
— Básico
Se puede usar la palabra clave export, para exponer partes de código a otros, la sintaxis básica es la siguiente:
Ejemplo:
— Exportación agrupada
En el ejemplo anterior, no se ve mal el código, pero creo que es un poco incomodo estar anteponiendo la palabra export para cada una de las variables, funciones o clase; por fortuna, tenemos otra forma más sencilla de exportar, que es agrupándolos, la sintaxis es la siguiente:
Veamos el mismo ejemplo de arriba:
Otra ventaja no es necesario exportar todo lo que hagamos en ese archivo de JavaScript, veamos un ejemplo…
Ejemplo: Imagina que tenemos una función calculadora, y que internamente ejecuta la función solicitada y retorna un valor; quizás como medida de precaución no queremos exponer nuestras funciones (sum, rest, multiply, etc), con la exportación de módulos es bastante sencillo hacerlo:
De esta forma solo estamos dejando exportar la función calculator, por lo que las constantes PI, IVA y las funciones sum, multiply, etc. no van a poder acceder, entonces nosotros como desarrolladores vamos a tener el control de que si y que no exponer para exportar.
— Renombrar exportaciones
Algo que también es bastante útil al momento de exportar es el poder cambiar el nombre usando la palabra clave as, veamos la sintaxis:
Ejemplo:
Imagina que tenemos la función llamada sdp_exp (un nombre que no dice absolutamente nada), la idea es que sea un nombre válido y amigable:
Y donde se importe se podrá usar el enlace sum.
— Valores predeterminados
Otra característica que tiene el export, es poder asignar uno por default (cuando veamos la importación, entenderemos bien por que es útil), la sintaxis es la siguiente:
Ejemplo:
— Resumiendo
Al momento de utilizar export, tengamos en cuenta lo siguiente:
- No es posible exportar dentro de una condición, función o clase, por lo que las exportaciones no pueden ser dinámicas, esto es para que el motor de JavaScript pueda determinar de forma estática que se exportará.
- Solo podemos exportar a nivel superior del módulo.
- Toda variable (var, let, const), función o clase que se anteponga export (ya sea de manera directa o entre llaves), va a poder utilizarse en otros archivos donde se importe.
b — Importar
— Básico (enlace único o enlaces múltiples)
Cuando creamos un módulo exportable, podemos acceder a la funcionalidad utilizando la palabra clave import, la sintaxis básica es la siguiente:
Importante: No olvidar /, ./o ../ al momento de importar el archivo para una mejor compatibilidad entre navegadores y Node.js.
- / — Cuando es desde el directorio raíz
- ./ — Cuando es desde el directorio actual
- ../ — Cuando es desde el directorio padre
- http://.. — Formato de URL completa
Ejemplo:
Imagina que tenemos un archivo llamado module.js y que estamos exportando user, age, PI, sum, Foo…
Como podemos observar en nuestro ejemplo, estamos importando SOLO los enlaces que necesitamos, esto quiere decir que no es necesario importar todas, y al hacer esto el motor de JavaScript pueda determinar de forma estática que se importará.
Importante: La lista de enlaces para importar se parece a un objeto desestructurado, pero no lo es.
— Importando todo el módulo
Imagina que tenemos un módulo llamado operations.js y que tenemos todos esto valores para exportar: addition, subtraction, division, multiplication, squareRoot, etc… Creo que al momento de importar, para no estar poniendo cada uno de los valores, podemos solucionarlo con el operador *, veamos su sintaxis:
Ejemplo:
— Enlaces importados solo de lectura
Otra ventaja que nos da el importar módulos es la prohibición de reasignación de variables, funciones o clase, veamos un ejemplo:
Ejemplo:Imagina que tenemos el archivo calculator.js, donde exportamos la función sum…
Esto es bastante útil para evitar errores al momento de querer cambiar un valor de un enlace importado.
Pero hay que tener mucho cuidado con las variables de los enlaces importados, ya que, aunque el módulo que importa el enlace no puede cambiar su valor, el módulo que exporta ese identificador sí puede, veamos un ejemplo:
— Renombrar importaciones
Así como al exportar podemos asignarle un nombre, al momento de importar ocurre lo mismo y es usando la palabra clave as, veamos la sintaxis:
Ejemplo:
Imagina que tenemos los siguientes dos archivos (basic.js y calculator.js) y cada uno tiene sus exportaciones, algo así:
Podemos observar en ambos módulos se repite el nombre sum al momento de exportar, aquí es donde nos ayuda la palabra clave as; veamos el ejemplo:
— Valores predeterminados
En la parte de exportar, tocamos el tema de exportar valores predeterminados (default), donde teníamos el siguiente ejemplo:
Ahora para importar un valor predeterminado, la sintaxis es la siguiente:
Ejemplo:
Importante: Observa que cuando se importa un valor predeterminado, NO se utilizan llaves.
Cuando importamos, también podemos combinar un enlace predeterminado con otros no predeterminados, veamos el siguiente ejemplo:
Podemos observar en nuestro ejemplo, que el valor predeterminado no se agrupa dentro de las llaves, mientras que TODOS los NO predeterminados SI se van agrupar dentro de las llaves.
— Resumiendo
Al momento de utilizar import, tengamos en cuenta lo siguiente:
- No olvidar /, ./o ../ al momento de importar el archivo para una mejor compatibilidad entre navegadores y Node.js.
- La lista de enlaces NO es un objeto desestructurado.
- Podemos reexportar un enlace importado.
- No es posible importar dentro de una condición, función o clase, por lo que las importaciones no pueden ser dinámicas, esto es para que el motor de JavaScript pueda determinar de forma estática que importará.
- Solo podemos importar a nivel superior del módulo.
Entonces los módulos de ES6 son muy útiles al momento de reutilizar, separar y encapsular pedazos de código, procuremos no aumentarle complejidad a la aplicación, si no de tener la capacidad de poder escalar y eliminar de manera sencilla sin romper la aplicación.
En la siguiente entrega vamos a ver JavaScript — Clases [ES6]
La entrega pasada vimos JavaScript — Symbols & Generadores ES6 (Parte III)
Bibliografía y links que te puede interesar…