JavaScript Iterators: A Complete Guide

JavaScript iterators are objects that allow you to loop over any data structure, such as arrays, strings, maps, sets, and custom objects. They provide a consistent and simple way to access the elements of a collection, one at a time.

In this article, you will learn:

  • What are iterators and iterables in JavaScript
  • How to use the next() method and the iterator protocol
  • How to use the for...of loop and the iterable protocol
  • How to create and use custom iterators and iterables
  • What are the benefits and use cases of iterators and iterables

Iterators and Iterables

An iterator is an object that conforms to the iterator protocol, which means it has a next() method that returns an object with two properties: value and done. The value property is the next element in the sequence, and the done property is a boolean that indicates whether the sequence has ended or not.

An iterable is an object that conforms to the iterable protocol, which means it has a Symbol.iterator property that returns an iterator for the object. The Symbol.iterator is a special symbol that represents the default iterator for an object.

All built-in data structures in JavaScript, such as arrays, strings, maps, sets, and arguments, are iterables. They have a Symbol.iterator property that returns a built-in iterator for them. For example, the array iterator returns each element of the array in order, and the string iterator returns each code point of the string in order.

You can use the for...of loop to iterate over any iterable object, without knowing its internal structure or how to access its elements. The for...of loop calls the Symbol.iterator property of the object and uses the returned iterator to get the values.

Javascript
                        
let array = [1, 2, 3, 4, 5];
let string = "Hello";

for (let value of array) {
  console.log(value); // 1, then 2, then 3, then 4, then 5
}

for (let value of string) {
  console.log(value); // H, then e, then l, then l, then o
}

The next() Method and the Iterator Protocol

The next() method of an iterator is the core of the iterator protocol. It returns an object with two properties: value and done. The value property is the next element in the sequence, and the done property is a boolean that indicates whether the sequence has ended or not.

You can call the next() method explicitly on an iterator object to get the values one by one. For example, you can get the iterator of an array by calling its Symbol.iterator property, and then call the next() method on it.

Javascript
                        
let array = [1, 2, 3, 4, 5];
let iterator = array[Symbol.iterator](); // get the iterator of the array

console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 4, done: false}
console.log(iterator.next()); // {value: 5, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

The next() method can also accept an argument, which is the value to be returned by the previous yield expression in a generator function. This allows you to communicate with the generator and pass values to it. For example, you can create a generator function that calculates the factorial of a number, and pass the number to it using the next() method.

Javascript
                        
function* factorial() {
  let n = yield; // get the number from the next() method
  let result = 1;
  for (let i = 1; i <= n; i++) {
    result *= i;
  }
  return result;
}

let iterator = factorial(); // get the iterator of the generator function
iterator.next(); // start the generator
console.log(iterator.next(5)); // pass 5 to the generator and get the factorial of 5
// {value: 120, done: true}

The for...of Loop and the Iterable Protocol

The for...of loop is a convenient way to iterate over any iterable object, without knowing its internal structure or how to access its elements. The for...of loop calls the Symbol.iterator property of the object and uses the returned iterator to get the values.

The for...of loop is similar to the for...in loop, but there are some differences. The for...in loop iterates over the enumerable properties of an object, which may include inherited and non-iterable properties. The for...of loop iterates over the values of an iterable object, which are determined by the object itself. For example, the for...in loop will iterate over the array indexes and the length property, while the for...of loop will iterate over the array elements.

Javascript
                        
let array = [1, 2, 3, 4, 5];

for (let key in array) {
  console.log(key); // 0, then 1, then 2, then 3, then 4, then length
}

for (let value of array) {
  console.log(value); // 1, then 2, then 3, then 4, then 5
}

The for...of loop can also be used with other iterable objects, such as strings, maps, sets, and arguments. For example, you can use the for...of loop to iterate over the characters of a string, the key-value pairs of a map, the elements of a set, and the parameters of a function.

Javascript
                        
let string = "Hello";
let map = new Map([["a", 1], ["b", 2], ["c", 3]]);
let set = new Set([1, 2, 3, 4, 5]);
function foo(...args) {
  for (let arg of args) {
    console.log(arg);
  }
}

for (let char of string) {
  console.log(char); // H, then e, then l, then l, then o
}

for (let [key, value] of map) {
  console.log(`${key}: ${value}`); // a: 1, then b: 2, then c: 3
}

for (let value of set) {
  console.log(value); // 1, then 2, then 3, then 4, then 5
}

foo(6, 7, 8); // 6, then 7, then 8

Custom Iterators and Iterables

You can also create and use your own custom iterators and iterables, by implementing the iterator and iterable protocols. This allows you to define how to iterate over any data structure, such as a tree, a graph, a matrix, or a custom object.

To create a custom iterator, you need to define an object that has a next() method that returns an object with value and done properties. To create a custom iterable, you need to define an object that has a Symbol.iterator property that returns an iterator for the object.

For example, you can create a custom iterable object that represents a range of numbers, and a custom iterator object that returns the numbers in the range.

Javascript
                        
// Define a custom iterable object that represents a range of numbers
function Range(from, to) {
  this.from = from;
  this.to = to;
}

// Define the Symbol.iterator property for the Range object
Range.prototype[Symbol.iterator] = function () {
  // Return a custom iterator object for the Range object
  return {
    current: this.from,
    last: this.to,
    // Define the next() method for the iterator object
    next() {
      // If the current value is less than or equal to the last value
      if (this.current <= this.last) {
        // Return the current value and increment it
        return { value: this.current++, done: false };
      } else {
        // Return the done value
        return { done: true };
      }
    },
  };
};

// Create a Range object
let range = new Range(1, 5);

// Iterate over the Range object using the for...of loop
for (let value of range) {
  console.log(value); // 1, then 2, then 3, then 4, then 5
}

Benefits and Use Cases of Iterators and Iterables

  1. One of the benefits of iterators and iterables is that they provide a consistent and simple way to access the elements of any data structure, one at a time. You don't need to know the internal structure or how to access the elements of the data structure, you just need to use the next() method or the for...of loop.
  2. Another benefit of iterators and iterables is that they enable lazy evaluation, which means they only produce values when requested, and not in advance. This can improve the performance and memory efficiency of your code, especially when dealing with large or infinite sequences.
  3. A third benefit of iterators and iterables is that they allow you to create custom data structures and define how to iterate over them. This gives you more flexibility and control over your data and how to manipulate it.

Some of the common and useful use cases for iterators and iterables are:

  1. Generating infinite sequences, such as the Fibonacci numbers, the prime numbers, or the natural numbers.
  2. Implementing pagination, which allows you to divide a large collection of data into smaller chunks and fetch them on demand.
  3. Implementing coroutines, which are functions that can suspend and resume their execution, and communicate with each other using the yield and next() methods.
  4. Implementing data pipelines, which allow you to chain multiple iterators and iterables together and process the data in a streaming fashion.

Conclusion

In this article, you learned about JavaScript iterators and iterables, which are objects that allow you to loop over any data structure, one at a time. You learned how to use the next() method and the iterator protocol, how to use the for...of loop and the iterable protocol, how to create and use custom iterators and iterables, and what are the benefits and use cases of iterators and iterables.

Iterators and iterables are a powerful and elegant feature of JavaScript that can help you access and manipulate any data structure, regardless of its internal structure or size. They can also enable lazy evaluation, which can improve the performance and memory efficiency of your code. Iterators and iterables are widely supported by modern browsers and Node.js, so you can start using them today.