Language: EN

javascript-iterables-e-iteradores

What are Iterables and Iterators in JavaScript

In JavaScript, iterables are objects that allow us to traverse and access the elements of a collection of data sequentially and easily.

In simple terms, iterables and iterators are a tool that allows us to “iterate” over the values of a collection.

Unlike traditional methods of direct access to elements (like indices in arrays), iterators allow traversing collections without needing direct access to the internal structure.

Iterables can be traversed sequentially using a for...of loop or other functions that work with iterables, such as map(), filter(), reduce(), among others.

How iterables work

Under the hood, an iterable is any object that implements the Symbol.iterator method, which returns an iterator.

On the other hand, an iterator is simply an object that implements a next() method. This method returns an object with two properties:

  • value: The current value of the iteration.
  • done: A boolean value (true or false) that indicates whether the iteration has finished.

For example, when using an iterator to traverse an array, each call to next() will return the next value of the array until the end is reached.

In summary,

  • Iterable: An object that has a Symbol.iterator method that returns an iterator.
  • Iterator: An object with the next() method that returns an object with the value and done properties.

Using iterators with loops

Manual iteration with next()

To use an iterator, we can call it manually using the next() method.

const iterable = [10, 20, 30];
const iterator = iterable[Symbol.iterator]();

let result = iterator.next();
while (!result.done) {
  console.log(result.value); // Prints the values 10, 20, 30
  result = iterator.next();
}

In this example,

  • We start the iteration with iterator.next()
  • Then we use a while loop to continue iterating until the done property is marked as true.

Using for...of

A simpler and more common way to work with iterators is through the for...of loop, which automatically handles the iteration process.

const iterable = [1, 2, 3, 4];

for (const value of iterable) {
  console.log(value); // Prints 1, 2, 3, 4
}
  • This loop will take care of calling next() repeatedly until the entire iterable has been traversed
  • Internally, the loop uses the next() method of the iterator.

Creating our own iterators and iterables

We can also create our own iterables and iterators. This is useful when we need a custom way to iterate over a collection of data that is not standard, or when we are working with complex data structures.

Custom iterator

For example, imagine we want to create an iterator to traverse an object that contains a list of values. We can do this by implementing the next() method within an object:

const myIterator = {
  values: [10, 20, 30],
  currentIndex: 0,
  next() {
    if (this.currentIndex < this.values.length) {
      return { value: this.values[this.currentIndex++], done: false };
    }
    return { value: undefined, done: true };
  }
};

console.log(myIterator.next()); // { value: 10, done: false }
console.log(myIterator.next()); // { value: 20, done: false }
console.log(myIterator.next()); // { value: 30, done: false }
console.log(myIterator.next()); // { value: undefined, done: true }

In this example,

  • We have created an object myIterator that acts as an iterator for an array of numbers.
  • The currentIndex property keeps track of the current index in the array
  • The next() method returns the next value in the sequence until the end is reached.

Custom iterable

To create an iterable object, we must implement the Symbol.iterator method, which is a type of symbol property in JavaScript.

class CustomCollection {
    constructor(...items) {
        this.items = items;
    }

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

        return {
            next() {
                if (index < items.length) {
                    return { value: items[index++], done: false };
                } else {
                    return { value: undefined, done: true };
                }
            }
        };
    }
}

const collection = new CustomCollection('x', 'y', 'z');

for (const item of collection) {
    console.log(item);
}
// Output:
// x
// y
// z

In this example,

  • CustomCollection is an object that implements the Symbol.iterator method
  • This returns an iterator with the next() method that returns the values of the items property one by one until the end of the sequence is reached.

Using generators as iterables

We can also use generators to create iterable objects in a more concise and readable way.

function* myGenerator() {
  yield 'Hello';
  yield 'World';
}

const iterator = myGenerator();

for (const value of iterator) {
  console.log(value);
}

In this example, the generator myGenerator creates an iterable object that yields the values “Hello” and “World” when traversed with a for...of loop.