
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 obtenhamosdone: true
; - após cada chamada
next
no corpo do loop, a propriedade é usadavalue
.
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.