Iteradores - JavaScript
Iteradores são uma abstração para percorrer conjuntos de dados, sendo utilizados para organizar o acesso sequencial a elementos de conjuntos de dados como arrays, objetos Set
Map
for
of
const people = ["Tom", "Bob", "Sam"];
for (const person of people) {
console.log(person);
}
No ciclo for
of
of
Obtendo um Iterador
Qualquer objeto iterável, como um array, Map
Set
Symbol.iterator
const people = ["Tom", "Bob", "Sam"];
// obtemos o iterador do array
const iterator = people[Symbol.iterator]();
console.log(iterator); // Array Iterator {}
Aqui, obtemos o iterador do array, portanto, será exibido algo como Array Iterator {}
Outro exemplo envolve uma string que também é um objeto iterável e pode ser percorrida caracter por caracter:
const username = "Tom";
for (let char of username) {
console.log(char);
}
Para a string, também podemos obter um iterador:
const username = "Tom";
// obtemos o iterador da string
const iterator = username[Symbol.iterator]();
console.log(iterator); // StringIterator {}
O iterador da string é do tipo StringIterator
É importante observar que diferentes tipos podem ter métodos adicionais para obter um iterador. Por exemplo, os arrays têm o método entries()
const people = ["Tom", "Bob", "Sam"];
console.log(people.entries()); // Array Iterator {}
Método next dos Iteradores
Os iteradores fornecem o método next()
value
done
{value, done}
A propriedade value
done
done
true
next()
{done: true}
Por exemplo:
const people = ["Tom", "Bob", "Sam"];
const iter = people[Symbol.iterator]();
const result = iter.next();
console.log(result); // {value: "Tom", done: false}
Aqui, ao chamarmos o método next()
{value: "Tom", done: false}
Podemos ver que o objeto atual representa a string "Tom", e o valor done: false
Podemos chamar o método next()
const people = ["Tom", "Bob", "Sam"];
const iter = people[Symbol.iterator]();
console.log(iter.next()); // {value: "Tom", done: false}
console.log(iter.next()); // {value: "Bob", done: false}
console.log(iter.next()); // {value: "Sam", done: false}
console.log(iter.next()); // {value: undefined, done: true}
Usando o método next()
const people = ["Tom", "Bob", "Sam"];
const iter = people[Symbol.iterator]();
let item;
while (!(item = iter.next()).done) {
console.log(item.value);
}
No ciclo while
next()
item
item = iter.next()
E verificamos sua propriedade done: se for false
No ciclo, acessamos a propriedade value do objeto obtido:
console.log(item.value);
Saída do console:
Tom Bob Sam
No entanto, isso é redundante, pois todas as coleções que retornam iteradores suportam a iteração usando o ciclo for
of
Criando seu próprio Iterador
Para criar um iterador que percorre um array de trás para frente, podemos implementar uma função que define esse comportamento específico:
const people = ["Tom", "Bob", "Sam"];
function reverseArrayIterator(array) {
let count = array.length;
return {
next: function() {
if (count > 0) {
return {
value: array[--count],
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
};
};
const iter = reverseArrayIterator(people);
let item;
while (!(item = iter.next()).done) {
console.log(item.value);
}
Aqui, a variável count
next()
next()
done
false
value
count
Quando a variável count
next()
done
true
value
undefined
Dessa forma, obtemos um iterador que percorre os elementos do array de trás para frente. A saída no console seria:
Sam Bob Tom
Para fazer com que esse iterador seja usado também em ciclos for
of
const people = ["Tom", "Bob", "Sam"];
function reverseArrayIterator() {
const array = this; // capturamos o contexto do array via 'this'
let count = array.length;
return {
next: function() {
if (count > 0) {
return {
value: array[--count],
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
};
};
// modificamos o iterador padrão do array people
people[Symbol.iterator] = reverseArrayIterator;
for (const person of people) {
console.log(person);
}
Nesse exemplo, foram feitas duas mudanças principais:
Dentro da função iteradora, usamos
para referenciar o array atual.this
A função do iterador é atribuída à propriedade
do arraySymbol.iterator
, substituindo o iterador padrão por nosso iterador personalizado.people
Dessa forma, quando usamos um ciclo for
of
Criando Objetos Iteráveis
Diferentes objetos podem ter suas próprias implementações de iteradores. Se necessário, podemos definir um objeto com seu próprio iterador. O uso de iteradores nos fornece uma maneira de criar um objeto que se comporta como uma coleção de elementos.
Para criar um objeto iterável, precisamos definir o método [Symbol.iterator]()
const iterable = {
[Symbol.iterator]() {
return {
next() {
// se ainda houver elementos
return { value: ..., done: false };
// se não houver mais elementos
return { value: undefined, done: true };
}
};
}
};
O [Symbol.iterator]()
next()
value
done
Se o nosso objeto tiver elementos, a propriedade value conterá o valor do elemento atual, e a propriedade done
false
Se não houver mais elementos disponíveis, a propriedade done
true
Por exemplo, vamos implementar um objeto iterável simples que retorna uma sequência de números:
const iterable = {
[Symbol.iterator]() {
return {
current: 1,
end: 3,
next() {
if (this.current <= this.end) {
return { value: this.current++, done: false };
}
return { done: true };
}
};
}
};
for (const value of iterable) {
console.log(value);
}
Aqui, o iterador retorna números de 1 a 3. Para rastrear o elemento atual no objeto que retorna o método, são definidas duas propriedades:
current: 1,
end: 3,
O incremento this.current++
next
Se o limite for alcançado, então retornamos o objeto
return { done: true };
Isso indica que não há mais elementos disponíveis para percorrer.
Podemos obter os elementos retornados pelo iterador:
const myIterator = iterable[Symbol.iterator](); // obtemos o iterador
console.log(myIterator.next()); // {value: 1, done: false}
console.log(myIterator.next()); // {value: 2, done: false}
console.log(myIterator.next()); // {value: 3, done: false}
console.log(myIterator.next()); // {done: true}
Inicialmente, obtemos o iterador na constante myIterator
next()
next()
{done: true}
No entanto, se quisermos percorrer nosso objeto e obter seus elementos, não precisamos acessar o método next()
iterable
for
of
for (const value of iterable) {
console.log(value);
}
Saída do console:
1 2 3
O loop for
of
next()
Vejamos outro exemplo:
// objeto da empresa
const company = {
// array de funcionários
employees: [
{name: "Tom", age: 39, position: "Senior Developer"},
{name: "Bob", age: 43, position: "Middle Developer"},
{name: "Sam", age: 28, position: "Junior Developer"},
]
};
// estabelecemos um iterador
company[Symbol.iterator] = function() {
const array = this.employees; // obtemos o array de funcionários
let current = 0;
return {
next() {
if (current < array.length) {
return { value: array[current++].name, done: false };
}
return { value: undefined, done: true };
}
};
};
for (const employee of company) {
console.log(employee);
}
Aqui, o objeto company
employees
company
employees
Tom Bob Sam