Iterables e Iterators: Um Guia Detalhado para JavaScript



Este artigo é uma introdução detalhada aos iteráveis ​​e iteradores em JavaScript. Minha principal motivação para escrever isso foi me preparar para aprender sobre geradores. Na verdade, eu planejava fazer experiências mais tarde combinando geradores e ganchos React. Se você estiver interessado, siga meu Twitter ou YouTube !



Na verdade, planejei começar com um artigo sobre geradores, mas logo ficou óbvio que é difícil falar sobre eles sem um bom entendimento de iteráveis ​​e iteradores. Vamos nos concentrar neles agora. Assumirei que você não sabe nada sobre este assunto, mas ao mesmo tempo nos aprofundaremos significativamente. Então, se você é alguma coisa sabe sobre iteráveis ​​e iteradores, mas não se sinta confortável em usá-los, este artigo irá ajudá-lo.



Introdução



Como você notou, estamos discutindo iteráveis e iteradores. São conceitos inter-relacionados, mas diferentes, portanto, ao ler o artigo, preste atenção em qual deles está sendo discutido em um caso específico.



Vamos começar com objetos iteráveis. O que é isso? Isso é algo que pode ser repetido, por exemplo:



for (let element of iterable) {
    // do something with an element
}

      
      





Observe que estamos apenas olhando os loops for ... of



que foram introduzidos no ES6 aqui. E os loops for ... in



são uma construção mais antiga à qual não iremos nos referir neste artigo.



Agora você pode estar pensando: "Ok, esta variável iterável é apenas um array!" Isso mesmo, os arrays são iteráveis. Mas agora existem outras estruturas em JavaScript nativo que você pode usar em um loop for ... of



. Ou seja, além dos arrays, existem outros objetos iteráveis.



Por exemplo, podemos iterar Map



, introduzido no ES6:



const ourMap = new Map();

ourMap.set(1, 'a');
ourMap.set(2, 'b');
ourMap.set(3, 'c');

for (let element of ourMap) {
    console.log(element);
}

      
      





Este código exibirá:



[1, 'a']
[2, 'b']
[3, 'c']

      
      





Ou seja, a variável element



em cada estágio de iteração armazena uma matriz de dois elementos. O primeiro é a chave, o segundo é o valor.



O fato de termos sido capazes de usar um loop for ... of



para iterar Map



prova que Map



são iteráveis. Novamente for ... of



, apenas objetos iteráveis podem ser usados em loops . Ou seja, se algo funciona com este loop, é um objeto iterável.



É engraçado que o construtor Map



aceita opcionalmente iteráveis ​​de pares de valores-chave. Ou seja, esta é uma forma alternativa de construir o mesmo Map



:



const ourMap = new Map([
    [1, 'a'],
    [2, 'b'],
    [3, 'c'],
]);

      
      





E como Map



é iterável, podemos fazer cópias dele com muita facilidade:



const copyOfOurMap = new Map(ourMap);

      
      





Agora temos dois diferentes Map



, embora armazenem as mesmas chaves com os mesmos valores.



Portanto, vimos dois exemplos de objetos iteráveis ​​- array e ES6 Map



. Mas ainda não sabemos como eles conseguiram ser iteráveis. A resposta é simples: existem iteradores associados a eles . Cuidado: os iteradores não são iteráveis .



Como um iterador é associado a um objeto iterável? Um iterável simples deve conter uma função em sua propriedade Symbol.iterator



. Quando chamada, a função deve retornar um iterador para este objeto.



Por exemplo, você pode recuperar um iterador de array:



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

console.log(iterator);

      
      





Este código é enviado para o console Object [Array Iterator] {}



. Agora sabemos que o array possui um iterador associado, que é algum tipo de objeto.



O que é um iterador?



É simples. Um iterador é um objeto que contém um método next



. Quando este método é chamado, ele deve retornar:



  • próximo valor em uma seqüência de valores;
  • informações sobre se o iterador terminou de gerar valores.


Vamos testar isso chamando um método next



em nosso iterador de array:



const result = iterator.next();

console.log(result);

      
      





Veremos o objeto no console { value: 1, done: false }



. O primeiro elemento da matriz que criamos é 1, e aqui ele apareceu como um valor. Também recebemos informações de que o iterador ainda não terminou, ou seja, ainda podemos chamar a função next



e obter alguns valores. Vamos tentar! Vamos chamá- next



lo mais duas vezes:



console.log(iterator.next());
console.log(iterator.next());

      
      





Recebido um por um { value: 2, done: false }



e { value: 3, done: false }



.



Existem apenas três elementos em nosso array. O que acontece se você ligar de novo next



?



console.log(iterator.next());

      
      





Desta vez veremos { value: undefined, done: true }



. Isso indica que o iterador está completo. Não adianta ligar novamente next



. Se fizermos isso, uma e outra vez receberemos um objeto { value: undefined, done: true }



. done: true



significa parar de iterar.



Agora você pode entender o que ele faz for ... of



nos bastidores:



  • o primeiro método [Symbol.iterator]()



    é chamado para obter o iterador;
  • o método next



    é chamado ciclicamente no iterador até que o obtenhamos done: true



    ;
  • após cada chamada next



    no corpo do loop, a propriedade é usada value



    .


Vamos escrever tudo isso em código:



const iterator = ourArray[Symbol.iterator]();

let result = iterator.next();

while (!result.done) {
    const element = result.value;

    // do some something with element

    result = iterator.next();
}

      
      





Este código é equivalente a este:



for (let element of ourArray) {
    // do something with element
}

      
      





Você pode verificar isso, por exemplo, inserindo em console.log(element)



vez de um comentário // do something with element



.



Crie seu próprio iterador



Agora sabemos o que são iteráveis ​​e iteradores. Surge a pergunta: "Posso escrever minhas próprias instâncias?"



Certamente!



Não há nada de misterioso sobre os iteradores. Esses são apenas objetos com um método next



que se comportam de uma maneira especial. Já descobrimos quais valores nativos em JS são iteráveis. Nenhum objeto foi mencionado entre eles. Na verdade, eles não são iterados nativamente. Considere um objeto como este:



const ourObject = {
    1: 'a',
    2: 'b',
    3: 'c'
};

      
      





Se iterarmos com ele for (let element of ourObject)



, obteremos um erro object is not iterable



.



Vamos escrever nossos próprios iteradores tornando tal objeto iterável!



Para fazer isso, você deve corrigir o protótipo Object



com seu próprio método [Symbol.iterator]()



. Já que corrigir o protótipo é uma prática ruim, vamos criar nossa própria classe estendendo Object



:



class IterableObject extends Object {
    constructor(object) {
        super();
        Object.assign(this, object);
    }
}

      
      





O construtor de nossa classe pega um objeto comum e copia suas propriedades em um objeto iterável (embora ainda não seja realmente iterável!).



Vamos criar um objeto iterável:



const iterableObject = new IterableObject({
    1: 'a',
    2: 'b',
    3: 'c'
})

      
      





Para tornar uma classe IterableObject



realmente iterável, precisamos de um método [Symbol.iterator]()



. Vamos adicionar.



class IterableObject extends Object {
    constructor(object) {
        super();
        Object.assign(this, object);
    }

    [Symbol.iterator]() {

    }
}

      
      





Agora você pode escrever um iterador real!



Já sabemos que deve ser um objeto com um método next



. Vamos começar com isso.



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        return {
            next() {}
        }
    }
}

      
      





Após cada chamada, next



você precisa retornar um objeto de visualização { value, done }



. Vamos fazer isso com valores fictícios.



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        return {
            next() {
                return {
                    value: undefined,
                    done: false
                }
            }
        }
    }
}

      
      





Dado um objeto iterável como este:



const iterableObject = new IterableObject({
    1: 'a',
    2: 'b',
    3: 'c'
})

      
      





vamos gerar pares de valor-chave, semelhante a como a iteração ES6 faz Map



:



['1', 'a']
['2', 'b']
['3', 'c']

      
      





Em nosso iterador, property



armazenaremos um array no valor [key, valueForThatKey]



. Observe que esta é a nossa solução em comparação com as etapas anteriores. Se quiséssemos escrever um iterador que retornasse apenas chaves ou apenas valores de propriedades, poderíamos fazê-lo sem problemas. Acabamos de decidir retornar pares de valores-chave agora.



Precisamos de um array do tipo [key, valueForThatKey]



. A maneira mais fácil de conseguir isso é com o método Object.entries



. Podemos usá-lo antes de criar o objeto iterador no método [Symbol.iterator]()



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        // we made an addition here
        const entries = Object.entries(this);

        return {
            next() {
                return {
                    value: undefined,
                    done: false
                }
            }
        }
    }
}

      
      





O iterador retornado no método acessará a variável graças ao fechamento do JavaScript entries



.



Também precisamos de uma variável de estado. Ele nos dirá qual par de valor-chave deve ser retornado na próxima chamada next



. Vamos adicionar:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        // we made an addition here
        let index = 0;

        return {
            next() {
                return {
                    value: undefined,
                    done: false
                }
            }
        }
    }
}

      
      





Observe que declaramos a variável index



c let



porque sabemos que planejamos atualizar seu valor após cada chamada next



.



Agora estamos prontos para retornar o valor real no método next



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                return {
                    // we made a change here
                    value: entries[index],
                    done: false
                }
            }
        }
    }
}

      
      





Foi fácil. Usamos apenas variáveis entries



e index



para acessar o par de valores-chave correto da matriz entries



.



Agora precisamos lidar com a propriedade done



, porque agora sempre será false



. Você pode criar mais uma variável além de entries



e index



, e atualizá-la após cada chamada next



. Mas existe uma maneira ainda mais fácil. Vamos verificar se index



a matriz está fora dos limites entries



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                return {
                    value: entries[index],
                    // we made a change here
                    done: index >= entries.length
                }
            }
        }
    }
}

      
      





Nosso iterador termina quando a variável index



é igual ou maior que seu comprimento entries



. Por exemplo, se y tem entries



comprimento 3, então ele contém valores nos índices 0, 1 e 2. E quando a variável index



é igual ou maior que 3, significa que não há mais valores restantes. Foram realizadas.



Este código quase funciona. Resta apenas uma coisa a acrescentar.



A variável index



começa em 0, mas ... não a atualizamos! Não é tão simples assim. Precisamos atualizar a variável após o retorno { value, done }



. Mas quando o devolvemos, o método next



para imediatamente, mesmo se houver algum código após a expressão return



. Mas podemos criar um objeto { value, done }



, armazená-lo em uma variável, atualizá-lo index



e só então retornar o objeto:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                const result = {
                    value: entries[index],
                    done: index >= entries.length
                };

                index++;

                return result;
            }
        }
    }
}

      
      





Depois de nossas alterações, a classe IterableObject



fica assim:



class IterableObject extends Object {
    constructor(object) {
        super();
        Object.assign(this, object);
    }

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                const result = {
                    value: entries[index],
                    done: index >= entries.length
                };

                index++;

                return result;
            }
        }
    }
}

      
      





O código funciona muito bem, mas ficou muito confuso. Isso porque mostra uma maneira mais inteligente, mas menos óbvia, de atualizar index



após a criação do objeto result



. Podemos apenas inicializar index



com -1! E embora seja atualizado antes de o objeto retornar next



, tudo funcionará bem, porque a primeira atualização substituirá -1 por 0.



Então, vamos fazer isso:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = -1;

        return {
            next() {
                index++;

                return {
                    value: entries[index],
                    done: index >= entries.length
                }
            }
        }
    }
}

      
      





Como você pode ver, agora não precisamos conciliar a ordem de criação result



e atualização do objeto index



. Durante a segunda chamada, ele index



será atualizado para 1 e retornaremos um resultado diferente e assim por diante. Tudo funciona como queríamos e o código parece muito mais simples.



Mas como verificamos a exatidão do trabalho? Você pode executar manualmente um método [Symbol.iterator]()



para instanciar um iterador e, em seguida, verificar diretamente os resultados das chamadas next



. Mas você pode fazer muito mais fácil! Foi dito acima que qualquer objeto iterável pode ser inserido em um loop for ... of



. Vamos fazer exatamente isso, registrando os valores retornados por nosso objeto iterável ao longo do caminho:



const iterableObject = new IterableObject({
    1: 'a',
    2: 'b',
    3: 'c'
});

for (let element of iterableObject) {
    console.log(element);
}

      
      





Trabalho! Isso é o que é exibido no console:



[ '1', 'a' ]
[ '2', 'b' ]
[ '3', 'c' ]

      
      





Legal! Começamos com um objeto que não podia ser usado em loops for ... of



, porque eles não contêm iteradores embutidos nativamente. Mas criamos o nosso próprio IterableObject



, que tem um iterador autoescrito associado.



Espero que agora você possa ver o potencial dos iteráveis ​​e iteradores. É um mecanismo que permite criar suas próprias estruturas de dados para trabalhar com funções JS como loops for ... of



, e elas funcionam como estruturas nativas! Esse é um recurso muito útil que pode simplificar muito seu código em certas situações, especialmente se você planeja iterar suas estruturas de dados com frequência.



Além disso, podemos personalizar o que exatamente essas iterações devem retornar. Nosso iterador agora retorna pares de valores-chave. E se quisermos apenas valores? Fácil, basta reescrever o iterador:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        // changed `entries` to `values`
        const values = Object.values(this);
        let index = -1;

        return {
            next() {
                index++;

                return {
                    // changed `entries` to `values`
                    value: values[index],
                    // changed `entries` to `values`
                    done: index >= values.length
                }
            }
        }
    }
}

      
      





E é isso! Se agora iniciarmos o loop for ... of



, veremos no console:



a
b
c

      
      





Retornamos apenas os valores dos objetos. Tudo isso prova a flexibilidade dos iteradores autoescritos. Você pode fazer com que eles devolvam o que quiser.



Iteradores como ... objetos iteráveis



É muito comum as pessoas confundirem iteradores com iteráveis. Isso é um erro e tentei separar os dois nitidamente. Acho que sei a razão pela qual as pessoas os confundem com tanta frequência.



Acontece que os iteradores ... às vezes são iteráveis!



O que isto significa? Como você deve se lembrar, um iterável é o objeto ao qual um iterador está associado. Cada iterador JavaScript nativo tem um método [Symbol.iterator]()



que retorna outro iterador! Isso torna o primeiro iterador um objeto iterável.



Você pode verificar isso pegando um iterador retornado de uma matriz e chamando-o [Symbol.iterator]()



:



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

const secondIterator = iterator[Symbol.iterator]();

console.log(secondIterator);

      
      





Depois de executar este código, você verá Object [Array Iterator] {}



. Ou seja, um iterador não contém apenas outro iterador associado a ele, mas também um array.



Se você comparar os dois iteradores com, ===,



verifica-se que eles são exatamente os mesmos:



const iterator = ourArray[Symbol.iterator]();

const secondIterator = iterator[Symbol.iterator]();

// logs `true`
console.log(iterator === secondIterator);

      
      





A princípio, você pode achar estranho o comportamento de um iterador que é seu próprio iterador. Mas esse é um recurso muito útil. Você não pode inserir um iterador simples em um loop for ... of



, ele só aceita um objeto iterável - um objeto com um método [Symbol.iterator]()



.



No entanto, a situação em que um iterador é seu próprio iterador (e, portanto, um objeto iterável) oculta o problema. Como os iteradores JS nativos contêm métodos [Symbol.iterator]()



, você pode passá-los diretamente para os loops sem hesitação for ... of



.



Como resultado, este snippet:



const ourArray = [1, 2, 3];

for (let element of ourArray) {
    console.log(element);
}

      
      





e este:



const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

      
      





trabalhar perfeitamente e fazer a mesma coisa. Mas por que alguém usaria iteradores como esse em loops diretamente for ... of



? Às vezes, é simplesmente inevitável.



Primeiro, você pode precisar criar um iterador sem pertencer a nenhum iterável. Veremos este exemplo abaixo e não é incomum. Às vezes, simplesmente não precisamos do iterável em si.



E seria muito estranho se ter um iterador simples significasse que você não pode usá-lo em for ... of



. Claro, você pode fazer isso manualmente usando um método next



e, por exemplo, um loop while



, mas vimos que para isso você tem que escrever muito código, e repetitivo.



A solução é simples: se você deseja evitar o código clichê e usar um iterador em um loop for ... of



, é necessário tornar o iterador um objeto iterável.



Por outro lado, também obtemos iteradores frequentemente de métodos diferentes de [Symbol.iterator]()



. Por exemplo, ES6 Map



contém métodos entries



, values



e keys



. Todos eles retornam iteradores.



Se os iteradores JS nativos também não fossem objetos iteráveis, você não poderia usar esses métodos diretamente em loops for ... of



, como este:



for (let element of map.entries()) {
    console.log(element);
}

for (let element of map.values()) {
    console.log(element);
}

for (let element of map.keys()) {
    console.log(element);
}

      
      





Esse código funciona porque os iteradores retornados pelos métodos também são objetos iteráveis. Caso contrário, você teria que, digamos, envolver o resultado da chamada map.entries()



em algum objeto iterável estúpido. Felizmente, não precisamos fazer isso.



É considerada uma boa prática criar seus próprios objetos iteráveis. Especialmente se eles forem retornados de métodos diferentes de [Symbol.iterator]()



. Tornar um iterador um objeto iterável é muito fácil. Deixe-me mostrar um exemplo de um iterador IterableObject



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        // same as before

        return {
            next() {
                // same as before
            },

            [Symbol.iterator]() {
                return this;
            }
        }
    }
}

      
      





Criamos um método [Symbol.iterator]()



abaixo do método next



. Tornou este iterador seu próprio iterador apenas retornando this



, o que significa que ele retorna a si mesmo. Acima, já vimos como um iterador de array se comporta. Isso é suficiente para que nosso iterador trabalhe em loops, for ... of



mesmo diretamente.



Estado do iterador



Agora deve ser óbvio que cada iterador possui um estado associado a ele. Por exemplo, em um iterador, IterableObject



armazenamos um estado - uma variável index



- como um encerramento. E nós o atualizamos após cada etapa de iteração.



O que acontece depois que o processo de iteração é concluído? O iterador torna-se inútil e você pode (deve!) Excluí-lo. Você pode ver que isso está acontecendo até mesmo pelo exemplo de objetos JS nativos. Vamos pegar um iterador de array e tentar executá-lo duas vezes em um loop for ... of



.



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

for (let element of iterator) {
    console.log(element);
}

      
      





Você pode esperar o console para exibir os números duas vezes 1



, 2



e 3



. Mas o resultado será assim:



1
2
3

      
      





Por quê?



Vamos chamar manualmente next



após o término do loop:



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

console.log(iterator.next());

      
      





O último log é enviado ao console { value: undefined, done: true }



.



É isso aí. Após o final do loop, o iterador entra no estado "concluído". Agora, ele sempre retornará um objeto { value: undefined, done: true }



.



Existe uma maneira de "redefinir" o estado do iterador para que ele possa ser usado uma segunda vez for ... of



? Em alguns casos, é possível, mas não faz sentido. Portanto, [Symbol.iterator]



é um método, não apenas uma propriedade. Você pode chamar o método novamente e obter outro iterador:



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

const secondIterator = ourArray[Symbol.iterator]();

for (let element of secondIterator) {
    console.log(element);
}

      
      





Agora tudo funciona conforme o esperado. Vamos ver por que vários looping de encaminhamento na matriz funcionam:



const ourArray = [1, 2, 3];

for (let element of ourArray) {
    console.log(element);
}

for (let element of ourArray) {
    console.log(element);
}

      
      





Todos os loops for ... of



usam iteradores diferentes ! Depois que o iterador e o loop terminam, esse iterador não é mais usado.



Iteradores e matrizes



Como usamos iteradores (embora indiretamente) em loops for ... of



, eles podem se parecer enganosamente com arrays. Mas existem duas diferenças importantes. Iterator e Array usam os conceitos de valores gananciosos e preguiçosos. Quando você cria um array, em um determinado momento no tempo ele tem um determinado comprimento e seus valores já foram inicializados. Claro, você pode criar um array sem nenhum valor, mas não é esse o caso. Meu ponto é que não é possível criar um array que inicialize seus valores somente após você se referir a eles por escrito array[someIndex]



. Pode ser possível contornar isso com um proxy ou algum outro truque, mas por padrão, os arrays de JavaScript não se comportam dessa maneira.



E quando eles dizem que uma matriz tem um comprimento, eles querem dizer que esse comprimento é finito. Não há matrizes infinitas em JavaScript.



Essas duas qualidades indicam a ganância das matrizes.



E os iteradores são preguiçosos .



Para demonstrar isso, criaremos dois de nossos iteradores: o primeiro será infinito, ao contrário dos arrays finitos, e o segundo inicializará seus valores apenas quando forem solicitados pelo usuário do iterador.



Vamos começar com um iterador infinito. Parece intimidador, mas é muito simples de criar: o iterador começa em 0 e retorna o próximo número na sequência a cada etapa. Para sempre.



const counterIterator = {
    integer: -1,

    next() {
        this.integer++;
        return { value: this.integer, done: false };
    },

    [Symbol.iterator]() {
        return this;
    }
}

      
      





E é isso! Começamos com uma propriedade de integer



-1. Cada vez next



que o chamamos, incrementamos em 1 e o retornamos como um objeto value



. Observe que usamos o truque mencionado novamente: começamos com -1 para retornar 0.



Também dê uma olhada na propriedade done



. Ele vai sempre ser falsa. Este iterador não termina!



Além disso, tornamos o iterador um iterável, dando a ele uma implementação simples [Symbol.iterator]()



.



Uma última coisa: este é o caso que mencionei acima - criamos um iterador, mas ele não precisa de um pai iterável para funcionar.



Agora, vamos tentar este iterador em um loop for ... of



. Você só precisa se lembrar de parar o loop em algum ponto, caso contrário, o código será executado para sempre.



for (let element of counterIterator) {
    if (element > 5) {
        break;
    }
    
    console.log(element);
}

      
      





Após o lançamento, veremos no console:



0
1
2
3
4
5

      
      





Na verdade, criamos um iterador infinito que retorna quantos números você desejar. E foi muito fácil fazer!



Agora vamos escrever um iterador que não crie valores até que eles sejam solicitados.



Bem ... nós já fizemos isso!



Você notou que counterIterator



apenas um número de propriedade é armazenado em um determinado momento integer



? Este é o último número retornado na chamada next



. E essa é a mesma preguiça. Um iterador pode potencialmente retornar qualquer número (mais precisamente, um inteiro positivo). Mas ele os cria apenas quando são necessários: quando o método é chamado next



.



Pode parecer muito enganador. Afinal, os números são criados rapidamente e não ocupam muito espaço na memória. Mas se você estiver trabalhando com objetos muito grandes que ocupam muita memória, às vezes substituir arrays por iteradores pode ser muito útil, acelerando o programa e economizando memória.



Quanto maior o objeto (ou mais tempo leva para ser criado), maior o benefício.



Outras maneiras de usar iteradores



Até agora, consumimos apenas iteradores em um loop for ... of



ou manualmente usando o next



. Mas essas não são as únicas maneiras.



Já vimos que o construtor Map



considera iteráveis ​​como um argumento. Você também pode Array.from



converter facilmente um iterável em uma matriz usando o método . Mas tenha cuidado! Como eu disse, a preguiça do iterador às vezes pode ser uma grande vantagem. A conversão para um array acaba com a preguiça. Todos os valores retornados pelo iterador são inicializados imediatamente e colocados em uma matriz. Isso significa que, se tentarmos converter infinito counterIterator



em array, isso levará ao desastre. Array.from



será executado para sempre sem retornar um resultado. Portanto, antes de converter um iterável / iterador em um array, você precisa ter certeza de que a operação é segura.



Curiosamente, os iteráveis ​​também funcionam bem com o operador de propagação (...



.) Lembre-se de que isso funciona da mesma Array.from



maneira quando todos os valores do iterador são gerados de uma vez. Por exemplo, você pode criar sua própria versão usando o operador spread Array.from



. Basta aplicar o operador ao iterável e, em seguida, colocar os valores em uma matriz:



const arrayFromIterator = [...iterable];

      
      





Você também pode obter todos os valores do iterável e aplicá-los à função:



someFunction(...iterable);

      
      





Conclusão



Espero que agora você compreenda o título do artigo Objetos Iteráveis e Iteradores. Aprendemos o que são, como diferem, como usá-los e como criar nossas próprias instâncias. Agora estamos totalmente prontos para trabalhar com geradores. Se você estiver familiarizado com iteradores, passar para o próximo tópico não deve ser muito difícil.



All Articles