Iteradores y generadores ES6

    Los iteradores y generadores suelen ser un pensamiento secundario al escribir código, pero si puede tomar unos minutos para pensar en cómo usarlos para simplificar su código, le ahorrarán mucha depuración y complejidad. Con los nuevos iteradores y generadores de ES6, JavaScript obtiene una funcionalidad similar a Iterable de Java, lo que nos permite personalizar nuestra iteración en objetos.

    Por ejemplo, si tiene un objeto Graph, puede usar fácilmente un generador para recorrer los nodes o bordes. Esto hace que el código sea mucho más limpio al colocar la lógica transversal dentro del objeto Graph donde pertenece. Esta separación de la lógica es una buena práctica, y los iteradores / generadores facilitan el seguimiento de estas mejores prácticas.

    Iteradores y generadores ES6

    Iteradores

    Usando iteradores, puede crear una forma de iterar usando el for...of construir para su objeto personalizado. En lugar de usar for...in, que solo recorre todas las propiedades del objeto, utilizando for...of nos permite hacer un iterador mucho más personalizado y estructurado en el que elegimos qué valores devolver para cada iteración.

    Bajo el capó, for...of en realidad está usando Symbol.iterator. Los símbolos de recuperación son una nueva característica de JavaScript ES6. los Symbol.iterator es un símbolo de propósito especial creado especialmente para acceder al iterador interno de un objeto. Entonces, podría usarlo para recuperar una función que itera sobre un objeto de matriz, así:

    var nums = [6, 7, 8];
    var iterator = nums[Symbol.iterator]();
    iterator.next();				// Returns { value: 6, done: false }
    iterator.next();				// Returns { value: 7, done: false }
    iterator.next();				// Returns { value: 8, done: false }
    iterator.next();				// Returns { value: undefined, done: true }
    

    Cuando usa el for...of constructo, esto es en realidad lo que se utiliza debajo. Observe cómo se devuelve cada valor posterior, junto con un indicador que le indica si está en el La gran mayoría de las veces no necesitará usar next() manualmente así, pero la opción está ahí en caso de que tenga un caso de uso que requiera un bucle más complejo.

    Puedes usar Symbol.iterator para definir la iteración especializada para un objeto. Entonces, digamos que tiene su propio objeto que es un envoltorio para oraciones, Sentence.

    function Sentence(str) {
        this._str = str;
    }
    

    Para definir cómo iteramos sobre las partes internas del objeto Sentence, proporcionamos una función de iterador prototipo:

    Sentence.prototype[Symbol.iterator] = function() {
        var re = /S+/g;
        var str = this._str;
    
        return {
            next: function() {
                var match = re.exec(str);
                if (match) {
                    return {value: match[0], done: false};
                }
                return {value: undefined, done: true};
            }
        }
    };
    

    Ahora, utilizando el iterador que acabamos de crear anteriormente (que contiene una cadena de expresiones regulares que coincide solo con palabras), podemos iterar fácilmente sobre las palabras de cualquier oración que proporcionemos:

    var s = new Sentence('Good day, kind sir.');
    
    for (var w of s) {
        console.log(w);
    }
    
    // Prints:
    // Good
    // day,
    // kind
    // sir.
    

    Generadores

    Los generadores ES6 se basan en lo que proporcionan los iteradores mediante el uso de una sintaxis especial para crear más fácilmente la función de iteración. Los generadores se definen utilizando el function* palabra clave. Dentro de una function*, puede devolver valores repetidamente usando yield. los yield La palabra clave se utiliza en las funciones del generador para pausar la ejecución y devolver un valor. Se puede considerar como una versión basada en generador del return palabra clave. En la siguiente iteración, la ejecución se reanudará en el último punto que yield se utilizó.

    function* myGenerator() {
        yield 'foo';
        yield 'bar';
        yield 'baz';
    }
    
    myGenerator.next();		// Returns {value: 'foo', done: false}
    myGenerator.next();		// Returns {value: 'bar', done: false}
    myGenerator.next();		// Returns {value: 'baz', done: false}
    myGenerator.next();		// Returns {value: undefined, done: true}
    

    O puede utilizar el for...of construir:

    for (var n of myGenerator()) {
        console.log(n);
    }
    
    // Prints
    // foo
    // bar
    // baz
    

    En este caso, podemos ver que el Generador luego se encarga de devolver {value: val, done: bool} objeto para usted, que se maneja bajo el capó en for...of.

    Entonces, ¿cómo podemos utilizar los generadores a nuestro favor? Volviendo a nuestro ejemplo anterior, podemos simplificar el Sentence iterador al siguiente código:

    Sentence.prototype[Symbol.iterator] = function*() {
        var re = /S+/g;
        var str = this._str;
        var match;
        while (match = re.exec(str)) {
            yield match[0];
        }
    };
    

    Observe cómo la función del iterador (ahora un generador) es mucho más pequeña que la versión anterior. Ya no necesitamos devolver un objeto con el next función, y ya no tenemos que ocuparnos de devolver el {value: val, done: bool} objeto. Si bien estos ahorros pueden parecer mínimos en este ejemplo, su utilidad se realizará fácilmente a medida que sus generadores crezcan en complejidad.

    Ventajas

    Como Jake Archibald señala, algunas ventajas de estos generadores son:

    • pereza: Los valores no se calculan de antemano, por lo que si no itera hasta el final, no habrá perdido el tiempo calculando los valores no utilizados.
    • Infinito: Dado que los valores no se calculan de antemano, puede obtener un conjunto infinito de valores. Solo asegúrate de salir del bucle en algún momento.
    • Iteración de cadenas: Gracias a Symbol.iterator, String ahora tiene su propio iterador para facilitar mucho la repetición de caracteres. Iterar sobre los símbolos de caracteres de una cadena puede ser un verdadero dolor de cabeza. Esto es especialmente útil ahora que JavaScript ES5 es compatible con Unicode.

      for (var símbolo de cadena) {
      console.log (símbolo);
      }

    Conclusión

    Si bien los iteradores y generadores no son grandes características adicionales, ayudan bastante a limpiar el código y mantenerlo organizado. Mantener la lógica de iteración con el objeto al que pertenece es una buena práctica, que parece ser gran parte del enfoque de las características de ES6. El estándar parece estar avanzando hacia la capacidad de estructura y facilidad de diseño de Java, al tiempo que mantiene la velocidad de desarrollo de lenguajes dinámicos.

    ¿Qué opinas de las nuevas funciones de ES6? ¿Qué tipo de casos de uso interesantes tiene para iteradores y generadores? ¡Háznoslo saber en los comentarios!

    Gracias a Jake Archibald por el gran artículo que detalla gran parte de cómo funcionan los iteradores y generadores en ES6.

     

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *