JavaScript Symbol.iterator Explained

In JavaScript, Symbol.iterator is a built-in well-known symbol that specifies the default iteration behavior for an object. This article covers the fundamental purpose of Symbol.iterator, the mechanics of the JavaScript Iteration Protocols, the built-in language features that rely on it, and how to implement custom iteration logic in your own objects.

The Role of Symbol.iterator

The primary purpose of Symbol.iterator is to make an object iterable. It serves as the key for a method that returns an iterator object conforming to the Iterator Protocol. When an object defines this method, it signals to JavaScript engines and APIs that its values can be sequentially accessed.

Under the hood, an iterator returned by [Symbol.iterator]() must implement a next() method. This next() method must return an object with two properties: - value: The current item in the sequence. - done: A boolean indicating whether the iteration sequence is complete (true) or ongoing (false).

Built-In Iterables

JavaScript includes several built-in data structures that have Symbol.iterator implemented on their prototypes by default:

Standard objects ({}) do not have a default Symbol.iterator method because there is no universal way to determine the iteration order or what data (keys, values, or entries) should be returned.

Language Features Powered by Symbol.iterator

When an object has a valid Symbol.iterator method, it automatically gains compatibility with numerous JavaScript operators and constructs, including:

  1. for...of Loops: Iterates directly over the values provided by the iterator.
  2. Spread Operator (...): Expands iterable elements into function arguments or array literals.
  3. Array Destructuring: Extracts values into distinct variables based on iterator position (e.g., const [first, second] = myIterable;).
  4. Array.from() and Promise.all(): Consumes iterables to create arrays or handle collections of promises.
  5. yield* Expression: Delegates to another generator or iterable inside a generator function.

Implementing a Custom Iterator

You can make any custom object iterable by defining a [Symbol.iterator] method on it.

Standard Iterator Pattern

const range = {
  from: 1,
  to: 3,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;

    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false };
        } else {
          return { done: true };
        }
      }
    };
  }
};

for (const num of range) {
  console.log(num); // Outputs: 1, 2, 3
}

console.log([...range]); // Outputs: [1, 2, 3]

Generator Function Pattern

Because generator functions return generator objects that naturally conform to the iterator protocol, you can simplify custom iteration by using a generator method:

const team = {
  lead: 'Alice',
  tester: 'Bob',
  developer: 'Charlie',
  *[Symbol.iterator]() {
    yield this.lead;
    yield this.tester;
    yield this.developer;
  }
};

for (const member of team) {
  console.log(member); // Outputs: Alice, Bob, Charlie
}

Summary

Symbol.iterator establishes a standardized interface for sequential data access across the JavaScript language. By implementing this well-known symbol, you ensure that custom data structures seamlessly integrate with modern syntax like for...of loops, destructuring, and spread operations.