La iteración de recopilaciones de datos mediante bucles tradicionales puede volverse rápidamente engorrosa y lenta, especialmente cuando se trata de grandes cantidades de datos.
Los generadores e iteradores de JavaScript brindan una solución para iterar de manera eficiente sobre grandes colecciones de datos. Utilizándolos, puede controlar el flujo de iteración, generar valores de uno en uno y pausar y reanudar el proceso de iteración.
Aquí cubrirá los aspectos básicos e internos de un iterador de JavaScript y cómo puede generar un iterador manualmente y usando un generador.
Iteradores de JavaScript
Un iterador es un objeto JavaScript que implementa el protocolo iterador. Estos objetos lo hacen al tener un próximo método. Este método devuelve un objeto que implementa el IteradorResultado interfaz.
El IteradorResultado La interfaz comprende dos propiedades:
hecho y valor. El hecho property es un valor booleano que devuelve FALSO si el iterador puede producir el siguiente valor en su secuencia o verdadero si el iterador ha completado su secuencia.El valor La propiedad es un valor de JavaScript devuelto por el iterador durante su secuencia. Cuando un iterador completa su secuencia (cuando hechoverdadero), esta propiedad devuelve indefinido.
Como su nombre lo indica, los iteradores le permiten "iterar" sobre objetos de JavaScript, como matrices o mapas. Este comportamiento es posible debido al protocolo iterable.
En JavaScript, el protocolo iterable es una forma estándar de definir objetos sobre los que puede iterar, como en un para... de bucle.
Por ejemplo:
constante frutas = ["Banana", "Mango", "Manzana", "Uvas"];
para (constante iterador de frutas) {
consola.log (iterador);
}
/*
Banana
mango
Manzana
Uvas
*/
Este ejemplo itera sobre el frutas matriz utilizando un para... de bucle. En cada iteración, registra el valor actual en la consola. Esto es posible porque las matrices son iterables.
Algunos tipos de JavaScript, como Arrays, Strings, Conjuntos y mapas, son iterables incorporados porque ellos (o uno de los objetos en su cadena de prototipo) implementan un @@iterador método.
Otros tipos, como los objetos, no se pueden iterar de forma predeterminada.
Por ejemplo:
constante iterObjeto = {
carros: ["Tesla", "BMW", "Toyota"],
animales: ["Gato", "Perro", "Hámster"],
alimento: ["Hamburguesas", "Pizza", "Pasta"],
};para (constante iterador de iterObjeto) {
consola.log (iterador);
}
// TypeError: iterObject no es iterable
Este ejemplo demuestra lo que sucede cuando intenta iterar sobre un objeto que no es iterable.
Hacer un objeto iterable
Para hacer que un objeto sea iterable, debe implementar un Símbolo.iterador método en el objeto. Para volverse iterable, este método debe devolver un objeto que implemente el IteradorResultado interfaz.
El Símbolo.iterador símbolo tiene el mismo propósito que @@iterador y se puede usar indistintamente en "especificación" pero no en el código como @@iterador no es una sintaxis de JavaScript válida.
Los bloques de código a continuación proporcionan un ejemplo de cómo hacer que un objeto sea iterable usando el iterObjeto.
Primero, agregue el Símbolo.iterador método para iterObjeto usando Una función declaración.
Al igual que:
iterObjeto[Símbolo.iterador] = función () {
// Los bloques de código posteriores van aquí...
}
A continuación, deberá acceder a todas las claves en el objeto que desea hacer iterable. Puede acceder a las claves mediante el Objeto.claves método, que devuelve una matriz de las propiedades enumerables de un objeto. Para devolver una matriz de iterObjetolas llaves de, pase el este palabra clave como argumento para Objeto.claves.
Por ejemplo:
dejar propiedadesobj = Objeto.llaves(este)
El acceso a esta matriz le permitirá definir el comportamiento de iteración del objeto.
A continuación, debe realizar un seguimiento de las iteraciones del objeto. Puede lograr esto usando variables de contador.
Por ejemplo:
dejar indicepropiedad = 0;
dejar childIndex = 0;
Utilizará la primera variable de contador para realizar un seguimiento de las propiedades del objeto y la segunda para realizar un seguimiento de los elementos secundarios de la propiedad.
A continuación, deberá implementar y devolver el próximo método.
Al igual que:
devolver {
próximo() {
// Los bloques de código posteriores van aquí...
}
}
Dentro de próximo método, deberá manejar un caso límite que ocurre cuando se ha iterado todo el objeto. Para manejar el caso límite, debe devolver un objeto con el valor ajustado a indefinido y hecho ajustado a verdadero.
Si no se maneja este caso, intentar iterar sobre el objeto dará como resultado un bucle infinito.
Aquí se explica cómo manejar el caso extremo:
si (índice de propiedad > objPropiedades.longitud- 1) {
devolver {
valor: indefinido,
hecho: verdadero,
};
}
A continuación, deberá acceder a las propiedades del objeto y sus elementos secundarios utilizando las variables de contador que declaró anteriormente.
Al igual que:
// Accediendo a las propiedades padre e hijo
constante propiedades = este[propiedadesobj[índicepropiedad]];
constante propiedad = propiedades[childIndex];
A continuación, debe implementar alguna lógica para incrementar las variables del contador. La lógica debe restablecer el niñoÍndice cuando no existen más elementos en la matriz de una propiedad y pasar a la siguiente propiedad en el objeto. Además, debe incrementar niñoÍndice, si todavía hay elementos en la matriz de la propiedad actual.
Por ejemplo:
// Lógica de incremento de índice
if (childIndex >= propiedades.longitud - 1) {
// si no hay más elementos en la matriz secundaria
// reiniciarniñoíndice
niñoÍndice = 0;
// Mover a la siguiente propiedad
índice de propiedad++;
} demás {
// Mover al siguiente elemento en la matriz secundaria
niñoÍndice++
}
Finalmente, devolver un objeto con el hecho propiedad establecida en FALSO y el valor propiedad establecida en el elemento secundario actual en la iteración.
Por ejemplo:
devolver {
hecho: FALSO,
valor: propiedad,
};
Su completado Símbolo.iterador La función debe ser similar al bloque de código a continuación:
iterObjeto[Símbolo.iterador] = función () {
constante propiedadesobj = Objeto.llaves(este);
dejar indicepropiedad = 0;
dejar childIndex = 0;devolver {
próximo: () => {
//Manejo de casos extremos
si (índice de propiedad > objPropiedades.longitud- 1) {
devolver {
valor: indefinido,
hecho: verdadero,
};
}// Accediendo a las propiedades padre e hijo
constante propiedades = este[propiedadesobj[índicepropiedad]];
constante propiedad = propiedades[childIndex];// Lógica de incremento de índice
if (childIndex >= propiedades.longitud - 1) {
// si no hay más elementos en la matriz secundaria
// reiniciarniñoíndice
niñoÍndice = 0;
// Mover a la siguiente propiedad
índice de propiedad++;
} demás {
// Mover al siguiente elemento en la matriz secundaria
niñoÍndice++
}
devolver {
hecho: FALSO,
valor: propiedad,
};
},
};
};
ejecutando un para... de bucle en iterObjeto después de esta implementación no arrojará un error ya que implementa un Símbolo.iterador método.
No se recomienda implementar iteradores manualmente, como hicimos anteriormente, ya que es muy propenso a errores y la lógica puede ser difícil de administrar.
Generadores de JavaScript
Un generador de JavaScript es una función que puede pausar y reanudar su ejecución en cualquier momento. Este comportamiento le permite producir una secuencia de valores a lo largo del tiempo.
Una función generadora, que es una función que devuelve un Generador, proporciona una alternativa a la creación de iteradores.
Puede crear una función generadora de la misma manera que crearía una declaración de función en JavaScript. La única diferencia es que debe agregar un asterisco (*) a la palabra clave de función.
Por ejemplo:
función* ejemplo () {
devolver"Generador"
}
Cuando llama a una función normal en JavaScript, devuelve el valor especificado por su devolver palabra clave o indefinido de lo contrario. Pero una función generadora no devuelve ningún valor inmediatamente. Devuelve un objeto Generador, que puede asignar a una variable.
Para acceder al valor actual del iterador, llame al próximo en el objeto Generador.
Por ejemplo:
constante gen = ejemplo();
consola.log (gen.next()); // { valor: 'Generador', hecho: verdadero }
En el ejemplo anterior, el valor propiedad procedía de un devolver palabra clave, terminando efectivamente el generador. Este comportamiento generalmente no es deseable con las funciones del generador, ya que lo que las distingue de las funciones normales es la capacidad de pausar y reiniciar la ejecución.
La palabra clave de rendimiento
El producir La palabra clave proporciona una forma de iterar a través de valores en generadores al pausar la ejecución de una función de generador y devolver el valor que le sigue.
Por ejemplo:
función* ejemplo() {
producir"Modelo S"
producir"Modelo X"
producir"Cibercamión"devolver"Tesla"
}constante gen = ejemplo();
consola.log (gen.next()); // { valor: 'Modelo S', hecho: FALSO }
En el ejemplo anterior, cuando el próximo se llama al método en el ejemplo generador, se detendrá cada vez que encuentre el producir palabra clave. El hecho la propiedad también se establecerá en FALSO hasta que se encuentra con un devolver palabra clave.
llamando al próximo método varias veces en el ejemplo generador para demostrar esto, tendrá lo siguiente como salida.
consola.log (gen.next()); // { valor: 'Modelo X', hecho: FALSO }
consola.log (gen.next()); // { valor: 'Cibercamión', hecho: FALSO }
consola.log (gen.next()); // { valor: 'Tesla', hecho: verdadero }
consola.log (gen.next()); // { valor: indefinido, hecho: verdadero }
También puede iterar sobre un objeto Generador usando el para... de bucle.
Por ejemplo:
para (constante iterador de generación) {
consola.log (iterador);
}
/*
Modelo S
Modelo X
camión cibernético
*/
Uso de iteradores y generadores
Aunque los iteradores y los generadores pueden parecer conceptos abstractos, no lo son. Pueden ser útiles cuando se trabaja con flujos de datos y colecciones de datos infinitos. También puede usarlos para crear identificadores únicos. Las bibliotecas de administración de estado, como MobX-State-Tree (MST), también las usan bajo el capó.