Skip to Content
Course content

107: Well-Known Symbols and Iterables Protocol

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years back, I was reviewing a PR from a developer who had built a custom Collection class to handle some complex data filtering. They had implemented a collection.getItems() method that returned an array, which they then looped over using a for...of loop. It worked, but it was clunky. Every time they wanted to use the collection in a spread operator or a for...of loop, they had to call that helper method first. When I asked why they didn't just make the collection itself iterable, they looked at me like I was speaking Greek. They didn't realize that JavaScript allows you to "hook" into the language's internal behavior using symbols.

Teaching JavaScript How to Iterate Your Objects

Normally, for...of loops only work on built-in iterables like Arrays, Maps, and Sets. If you try to loop over a plain object, JavaScript throws a TypeError: object is not iterable. But the engine isn't actually looking for "an array"β€”it's looking for a specific property on the object: Symbol.iterator.

This is what we call a "well-known symbol." These are predefined symbols created by the JavaScript specification that act as keys for internal language behaviors. If you define a method under Symbol.iterator, you are essentially telling JavaScript, "Hey, whenever someone tries to loop over me, use this logic."

class CustomRange {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }

  // This is the magic hook
  *[Symbol.iterator]() {
    for (let i = this.start; i <= this.end; i++) {
      yield i;
    }
  }
}

const range = new CustomRange(1, 5);
for (const num of range) {
  console.log(num); // 1, 2, 3, 4, 5
}

console.log([...range]); // [1, 2, 3, 4, 5]

In the example above, I used a generator function (the * syntax) because it's the cleanest way to implement the iterator protocol. It automatically returns an object with a next() method, which is exactly what the JS engine expects. Without the generator, you'd have to manually return an object with a next() function that returns { value: x, done: false }. Trust me, stick to generators unless you have a very weird edge case.

Customizing Object Identification with toStringTag

While Symbol.iterator is the most common well-known symbol you'll use, there are others that are incredibly handy for debugging. Have you ever logged a custom class instance and seen [object Object] in the console? It's useless. You have no idea which class that object actually belongs to.

You can fix this using Symbol.toStringTag. This symbol allows you to change the string representation of your object when Object.prototype.toString() is called (which is what console.log or string coercion often uses under the hood).

class UserSession {
  get [Symbol.toStringTag]() {
    return 'UserSession';
  }
}

const session = new UserSession();
console.log(Object.prototype.toString.call(session)); // [object UserSession]

I personally use this in almost every large-scale project I lead. When you're digging through a heap of objects in a debugger, seeing [object UserSession] instead of [object Object] saves you an annoying amount of time.




πŸ“‹ Practical Task

Implementing a FibonacciSequence Iterable

Your task is to create a class called FibonacciSequence. This class should take a limit as a constructor argument, representing the maximum number of elements in the sequence to generate.

Instead of storing the sequence in an array, you must implement the Symbol.iterator well-known symbol so that the class itself is iterable. The loop should calculate the Fibonacci numbers on the fly and yield them one by one until the limit is reached.

Requirements:

  • Implement the FibonacciSequence class with a limit property.
  • Use [Symbol.iterator] (preferably as a generator) to yield the sequence.
  • Implement Symbol.toStringTag so that the object identifies as "FibonacciSequence".
  • Verify your work by using a for...of loop to print the values and Object.prototype.toString.call() to verify the tag.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.