Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. JavaScript
Code

Uso de iteradores y generadores en JavaScript para optimizar código

by
Difficulty:AdvancedLength:MediumLanguages:

Spanish (Español) translation by Ana Paulina Figueroa Vazquez (you can also view the original English article)

Introducción

¿Alguna vez has necesitado recorrer una lista usando un ciclo, pero la operación tardó una cantidad significativa de tiempo en terminar? ¿alguna vez has tenido un programa que haya fallado debido a una operación que usó demasiada memoria? esto me pasó cuando intenté implementar una función que genera números primos.

Generar números primos hasta el valor de un millón tardó mucho más tiempo del que me hubiera gustado. Pero generar números hasta el valor de 10 millones fue imposible. Mi programa se bloqueaba o simplemente se congelaba. Ya estaba usando la criba de Eratóstenes, que se supone que es más eficiente para generar números primos que el enfoque de fuerza bruta.

Si te encuentras en una situación similar puedes intentar usando un algoritmo diferente. Existen algoritmos de búsqueda y ordenamiento que funcionan mejor con entradas más grandes. La desventaja es que estos algoritmos pueden ser más difíciles de comprender de forma inmediata. Otra opción es usar un lenguaje de programación diferente.

Quizá un lenguaje compilado pueda procesar el código significativamente más rápido. Pero usar otro lenguaje quizá no sea práctico. También puedes intentar usando múltiples hilos. Una vez más, quizá no sea práctico debido a que tu lenguaje de programación tendría que ser compatible con ellos.

Afortunadamente hay otra opción con JavaScript. Si tienes una tarea computacionalmente intensa, puedes usar iteradores y generadores para lograr más eficiencia. Los iteradores son una propiedad de ciertas colecciones de JavaScript.

Los iteradores mejoran la eficiencia permitiéndote consumir los elementos de una lista uno a la vez, como si fueran un flujo. Los generadores son un tipo especial de función que puede pausar la ejecución. Invocar a un generador te permite producir datos un fragmento a la vez sin la necesidad de almacenarlo primero en una lista.

Iteradores

Primero revisemos las diferentes maneras en las que puedes recorrer colecciones usando ciclos en JavaScript. Un ciclo de la forma for (inicial; condición; incremento) { ... } ejecutará los comandos contenidos en su cuerpo un número de veces específico. De forma similar, un ciclo while ejecutará los comandos de su cuerpo mientras su condición sea verdadera.

Puedes usar estos ciclos para recorrer una lista incrementando una variable índice durante cada iteración. Una iteración es una ejecución del cuerpo de un ciclo. Estos ciclos no conocen la estructura de tu lista. Éstos actúan como contadores.

Un ciclo for/in y un for/of están diseñados para iterar sobre estructuras de datos específicas. Iterar sobre una estructura de datos significa que estás pasando por cada uno de sus elementos. Un ciclo for/in recorre las claves de un objeto simple de JavaScript. Un ciclo for/of recorre los valores de un iterable. ¿Qué es un iterable? En pocas palabras, un iterable es un objeto que tiene un iterador. Entre los ejemplos de iterables se encuentran los arreglos y los conjuntos. El iterador es una propiedad del objeto que proporciona un mecanismo para recorrerlo.

Lo que hace especial a un iterador es la manera en la que recorre una colección. Otros ciclos necesitan cargar la colección completa por adelantado para recorrerla, mientras que un iterador solamente necesita saber la posición actual en la colección.

Puedes acceder al elemento actual invocando al método next (siguiente) del iterador. El método next devolverá el valor del elemento actual y un valor booleano para indicar si has llegado al final de la colección. El siguiente es un ejemplo de cómo crear un iterador a partir de un arreglo.

También puedes recorrer los valores del iterador usando un ciclo for/of. Usa este método cuando sepas que quieres acceder a todos los elementos del objeto. Ésta es la manera en la que usarías un ciclo para recorrer la lista anterior:

¿Por qué usarías un iterador? el uso de un iterador es benéfico cuando el costo computacional de procesar una lista es elevado. Si tienes una fuente de datos muy grande, ésta puede causar problemas en tu programa si intentas recorrerla con ciclos, ya que para hacer esto la colección completa debe cargarse.

Con un iterador puedes cargar los datos en partes. Esto es más eficiente debido a que solamente estás manipulando la parte de la lista que necesitas, sin incurrir en el costo adicional de procesar la lista completa.

Un ejemplo puede ser cargar datos de un archivo o base de datos y querer mostrar la información progresivamente en la pantalla. Puedes crear un iterador a partir de los datos y configurar un manejador de eventos para obtener algunos elementos cada vez que el evento ocurra. Este es un ejemplo de la forma en la que se vería una implementación de ese tipo:

Generadores

Si quieres construir una colección puedes hacerlo con un generador. Una función generadora puede devolver valores uno a la vez al pausar la ejecución en cada iteración. Al crear una instancia de un generador, es posible acceder a estos elementos usando un iterador. Esta es la sintaxis general para crear una función generadora:

El * significa que ésta es una función generadora. La palabra clave yield pausa nuestra función y proporciona el estado del generador en ese momento en particular. ¿Por qué usarías un generador? Puedes usar un generador cuando quieres producir un valor en una colección de forma algorítmica. Es particularmente útil si tienes una colección muy grande o infinita. Veamos un ejemplo para comprender cómo es que esto nos ayuda.

Supongamos que tienes un juego de billar en línea que has creado, y que quieres asignar jugadores a las salas de juego. Tu objetivo es generar todas las maneras en las que puedes elegir dos jugadores distintos de tu lista de 2,000 jugadores. Las combinaciones de dos jugadores generadas a partir de la lista ['a', 'b', 'c', 'd'] serían ab, ac, ad, bc, bd, cd. Ésta es una solución usando ciclos anidados:

Ahora intenta ejecutar la función con una lista de 2,000 elementos (puedes inicializar tu lista usando un ciclo for que agregue los números del 1 al 2,000 a un arreglo). ¿Qué pasa ahora cuando ejecutas tu código?.

Cuando ejecuto el código en un editor en línea la página web se bloquea. Cuando lo intento en la consola de Chrome puede ver el resultado apareciendo lentamente. Sin embargo, la CPU de mi computadora comienza a sobrecargarse y tengo que forzar el cierre de Chrome. Éste es el código modificado usando una función generadora:

Otro ejemplo es si quisiéramos generar los números de la secuencia de Fibonacci hasta el infinito. Ésta es una implementación:

Normalmente un ciclo infinito causaría un bloqueo en tu programa. La función fibGen es capaz de ejecutarse para siempre, ya que no hay una condición para detenerla. Pero dado que es un generador, tú controlas cuándo se ejecutará cada paso.

Repaso

Los iteradores y los generadores son útiles cuando quieres procesar una colección de forma incremental. Puedes lograr eficiencia al realizar un seguimiento del estado de la colección en vez de hacerlo para todos los elementos de la misma. Los elementos de la colección son evaluados uno a la vez, y la evaluación del resto de la colección se demora para hacerla más tarde.

Los iteradores proporcionan una manera eficiente de recorrer y manipular listas grandes. Los generadores proporcionan una manera eficiente de construir listas. Sería bueno que intentaras estas técnicas cuando de lo contrario usarías un algoritmo complejo o implementarías la programación en paralelo para optimizar tu código.

Si estás buscando recursos adicionales para estudiar o para usar en tu trabajo, revisa lo que tenemos disponible en Envato Market.

Recursos

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.