Leitores como você ajudam a apoiar o MUO. Quando você faz uma compra usando links em nosso site, podemos ganhar uma comissão de afiliado. Consulte Mais informação.

Iterar sobre coleções de dados usando loops tradicionais pode rapidamente se tornar complicado e lento, especialmente ao lidar com grandes quantidades de dados.

Geradores e iteradores de JavaScript fornecem uma solução para iterar eficientemente em grandes coleções de dados. Usando-os, você pode controlar o fluxo de iteração, produzir valores um por vez, pausar e retomar o processo de iteração.

Aqui você abordará o básico e interno de um iterador JavaScript e como gerar um iterador manualmente e usando um gerador.

Iteradores de JavaScript

Um iterador é um objeto JavaScript que implementa o protocolo do iterador. Esses objetos fazem isso por ter um próximo método. Este método retorna um objeto que implementa o IteratorResult interface.

O IteratorResult interface compreende duas propriedades: feito e valor. O feito propriedade é um booleano que retorna

instagram viewer
falso se o iterador pode produzir o próximo valor em sua sequência ou verdadeiro se o iterador tiver concluído sua sequência.

O valor propriedade é um valor JavaScript retornado pelo iterador durante sua sequência. Quando um iterador completa sua sequência (quando feitoverdadeiro), esta propriedade retorna indefinido.

Como o nome indica, os iteradores permitem que você “itere” sobre objetos JavaScript, como arrays ou mapas. Esse comportamento é possível devido ao protocolo iterável.

Em JavaScript, o protocolo iterável é uma forma padrão de definir objetos sobre os quais você pode iterar, como em um para de laço.

Por exemplo:

const frutas = ["Banana", "Manga", "Maçã", "Uvas"];

para (const iterador de frutas) {
console.log (iterador);
}

/*
Banana
manga
Maçã
Uvas
*/

Este exemplo itera sobre o frutas matriz usando um para de laço. Em cada iteração, ele registra o valor atual no console. Isso é possível porque os arrays são iteráveis.

Alguns tipos de JavaScript, como Arrays, Strings, Conjuntos e mapas, são iteráveis ​​integrados porque eles (ou um dos objetos em sua cadeia de protótipo) implementam um @@iterator método.

Outros tipos, como Objects, não são iteráveis ​​por padrão.

Por exemplo:

const iterObject = {
carros: ["Tesla", "BMW", "Toyota"],
animais: ["Gato", "Cachorro", "Hamster"],
comida: ["Hambúrgueres", "Pizza", "Massa"],
};

para (const iterador de iterObject) {
console.log (iterador);
}

// TypeError: iterObject não é iterável

Este exemplo demonstra o que acontece quando você tenta iterar sobre um objeto que não é iterável.

Tornando um objeto iterável

Para tornar um objeto iterável, você deve implementar um Symbol.iterator método no objeto. Para se tornar iterável, esse método deve retornar um objeto que implementa o IteratorResult interface.

O Symbol.iterator símbolo tem o mesmo propósito que @@iterator e pode ser usado de forma intercambiável em "especificação", mas não em código como @@iterator não é uma sintaxe JavaScript válida.

Os blocos de código abaixo fornecem um exemplo de como tornar um objeto iterável usando o iterObject.

Primeiro, adicione o Symbol.iterator método para iterObject usando uma função declaração.

Igual a:

iterObject[Símbolo.iterator] = função () {
// Os blocos de código subsequentes vão aqui...
}

Em seguida, você precisará acessar todas as chaves no objeto que deseja tornar iterável. Você pode acessar as chaves usando o Object.keys método, que retorna uma matriz das propriedades enumeráveis ​​de um objeto. Para retornar uma matriz de iterObject, passe as esse palavra-chave como um argumento para Object.keys.

Por exemplo:

deixar objPropriedades = Objeto.chaves(esse)

O acesso a esta matriz permitirá que você defina o comportamento de iteração do objeto.

Em seguida, você precisa acompanhar as iterações do objeto. Você pode conseguir isso usando variáveis ​​de contador.

Por exemplo:

deixar propertyIndex = 0;
deixar childIndex = 0;

Você usará a primeira variável de contador para acompanhar as propriedades do objeto e a segunda para acompanhar os filhos da propriedade.

Em seguida, você precisará implementar e retornar o próximo método.

Igual a:

retornar {
próximo() {
// Os blocos de código subsequentes vão aqui...
}
}

Dentro de próximo método, você precisará lidar com um caso extremo que ocorre quando todo o objeto foi iterado. Para lidar com o caso extremo, você deve retornar um objeto com o valor definido como indefinido e feito definido como verdadeiro.

Se este caso não for tratado, tentar iterar sobre o objeto resultará em um loop infinito.

Veja como lidar com o caso extremo:

se (índice de propriedade > objProperties.comprimento- 1) {
retornar {
valor: indefinido,
feito: verdadeiro,
};
}

Em seguida, você precisará acessar as propriedades do objeto e seus elementos filhos usando as variáveis ​​de contador declaradas anteriormente.

Igual a:

// Acessando propriedades pai e filho
const propriedades = esse[objProperties[propertyIndex]];

const propriedade = propriedades[childIndex];

Em seguida, você precisa implementar alguma lógica para incrementar as variáveis ​​do contador. A lógica deve redefinir o childIndex quando não houver mais elementos na matriz de uma propriedade e passar para a próxima propriedade no objeto. Além disso, deve incrementar childIndex, se ainda houver elementos na matriz da propriedade atual.

Por exemplo:

// Lógica de incremento do índice
if (childIndex >= properties.length - 1) {
// se não houver mais elementos no array filho
// reiniciarcriançaíndice
índicefilho = 0;

// Move para a próxima propriedade
índice de propriedade++;
} outro {
// Move para o próximo elemento no array filho
childIndex++
}

Finalmente, devolva um objeto com o feito propriedade definida como falso e a valor propriedade definida para o elemento filho atual na iteração.

Por exemplo:

retornar {
feito: falso,
valor: propriedade,
};

seu concluído Symbol.iterator função deve ser semelhante ao bloco de código abaixo:

iterObject[Símbolo.iterator] = função () {
const objPropriedades = Objeto.chaves(esse);
deixar propertyIndex = 0;
deixar childIndex = 0;

retornar {
próximo: () => {
// Tratando casos extremos
se (índice de propriedade > objProperties.comprimento- 1) {
retornar {
valor: indefinido,
feito: verdadeiro,
};
}

// Acessando propriedades pai e filho
const propriedades = esse[objProperties[propertyIndex]];

const propriedade = propriedades[childIndex];

// Lógica de incremento do índice
if (childIndex >= properties.length - 1) {
// se não houver mais elementos no array filho
// reiniciarcriançaíndice
índicefilho = 0;

// Move para a próxima propriedade
índice de propriedade++;
} outro {
// Move para o próximo elemento no array filho
childIndex++
}

retornar {
feito: falso,
valor: propriedade,
};
},
};
};

executando um para de loop em iterObject após esta implementação não lançará um erro, pois implementa um Symbol.iterator método.

A implementação manual de iteradores, como fizemos acima, não é recomendada, pois é muito propensa a erros e a lógica pode ser difícil de gerenciar.

Geradores de JavaScript

Um gerador de JavaScript é uma função que você pode pausar e retomar sua execução a qualquer momento. Esse comportamento permite produzir uma sequência de valores ao longo do tempo.

Uma função geradora, que é uma função que retorna um Gerador, oferece uma alternativa à criação de iteradores.

Você pode criar uma função geradora da mesma forma que criaria uma declaração de função em JavaScript. A única diferença é que você deve acrescentar um asterisco (*) à palavra-chave da função.

Por exemplo:

função* exemplo () {
retornar"Gerador"
}

Quando você chama uma função normal em JavaScript, ela retorna o valor especificado por seu retornar palavra-chave ou indefinido de outra forma. Mas uma função geradora não retorna nenhum valor imediatamente. Ele retorna um objeto Generator, que você pode atribuir a uma variável.

Para acessar o valor atual do iterador, chame o próximo método no objeto Generator.

Por exemplo:

const gen = exemplo();

console.log (gen.next()); // { valor: 'Gerador', feito: verdadeiro }

No exemplo acima, o valor propriedade veio de um retornar palavra-chave, encerrando efetivamente o gerador. Esse comportamento geralmente é indesejável com as funções do gerador, pois o que as distingue das funções normais é a capacidade de pausar e reiniciar a execução.

A palavra-chave de rendimento

O colheita A palavra-chave fornece uma maneira de iterar por meio de valores em geradores, pausando a execução de uma função geradora e retornando o valor que a segue.

Por exemplo:

função* exemplo() {
colheita"Modelo S"
colheita"Modelo X"
colheita"Caminhão Cibernético"

retornar"Tesla"
}

const gen = exemplo();

console.log (gen.next()); // { valor: 'Modelo S', feito: falso }

No exemplo acima, quando o próximo método é chamado no exemplo gerador, ele fará uma pausa toda vez que encontrar o colheita palavra-chave. O feito propriedade também será definida como falso até que encontra um retornar palavra-chave.

Chamando o próximo método várias vezes no exemplo gerador para demonstrar isso, você terá o seguinte como sua saída.

console.log (gen.next()); // { valor: 'Modelo X', feito: falso }
console.log (gen.next()); // { valor: 'Caminhão Cibernético', feito: falso }
console.log (gen.next()); // { valor: 'Tesla', feito: verdadeiro }

console.log (gen.next()); // { valor: indefinido, feito: verdadeiro }

Você também pode iterar sobre um objeto Generator usando o para de laço.

Por exemplo:

para (const iterador de geração) {
console.log (iterador);
}

/*
Modelo S
Modelo X
caminhão cibernético
*/

Usando iteradores e geradores

Embora iteradores e geradores possam parecer conceitos abstratos, eles não são. Eles podem ser úteis ao trabalhar com fluxos de dados infinitos e coleções de dados. Você também pode usá-los para criar identificadores exclusivos. As bibliotecas de gerenciamento de estado, como MobX-State-Tree (MST), também os usam sob o capô.