DEV Community

loading...

Custom Object Iterators in JavaScript

Steve Brownlee
Spencerian script, hand-stitched bookbinding, wax seals, coaching new crafters, helping people change their lives for the better. Despiser of complacency and maintenance mode.
・2 min read

Originally published on stevebrownlee.com

Imagine that you have an object in JavaScript that maintains a list of items, and you want the object itself to be iterable, but the object also has other important properties - such as metadata about the list of items.

Here's a fun and completely original example.

const se7enDwarves = {
    employment: "Miners",
    output: "Diamonds",
    members: new Map()
}
Enter fullscreen mode Exit fullscreen mode
se7enDwarves.members.set(1, "Sleepy")
se7enDwarves.members.set(2, "Grumpy")
se7enDwarves.members.set(3, "Happy")
se7enDwarves.members.set(4, "Bashful")
se7enDwarves.members.set(5, "Sneezy")
se7enDwarves.members.set(6, "Doc")
se7enDwarves.members.set(7, "Dopey")
Enter fullscreen mode Exit fullscreen mode

I now have an object that represents the seven dwarves. It holds their names in an iterable collection, but also other relevent data about them. I would love to be able to iterate over the object itself, instead of having to type in that horrible, troublesome property namespace of members.

I want to see the names of all the dwarves, so I use the spread syntax to iterate the object and place the items in an array.

[...se7enDwarves]

VM2417:1 Uncaught TypeError: se7enDwarves is not iterable
    at <anonymous>:1:5
Enter fullscreen mode Exit fullscreen mode

JavaScript, in all kinds of holy righteousness, tells me that I can't iterate an object like that.

Hmph.

Luckily, JavaScript allows developers to perform what is called metaprogramming. It's a feature of the language that allows us to modify some of the default behaviors of the underlying data structures available to us. I can make an object iterable.

se7enDwarves[Symbol.iterator] = function* () {
  const states = se7enDwarves.members.values()
  let state = null
  do {
    state = states.next().value
    if (typeof state !== "undefined") yield state
  } while (typeof state !== "undefined")
}
Enter fullscreen mode Exit fullscreen mode

High level overview... I made the object iterable. Thought I'd point that out in case it wasn't obvious from that "self-documenting code".

[...se7enDwarves]

(7) ["Sleepy", "Grumpy", "Happy", "Bashful", "Sneezy", "Doc", "Dopey"]
Enter fullscreen mode Exit fullscreen mode

Try it Out on jsfiddle

I create a JSFiddle for you to play around with this code yourself.

Discussion (2)

Collapse
kepta profile image
Kushan Joshi • Edited

Great article Steve, especially that you found a real world use case of making a custom iterable object. I had similar fun while writing How I learned to Stop Looping and Love the Iterator, though I wish I could add a section on generators.

One suggestion for you to improve the iterator of your se7enDwarves, is that you can redirect iteration to another iterable using yield *:

se7enDwarves[Symbol.iterator] = function* () {
  yield * se7enDwarves.members.values();
}

[...se7enDwarves]

(7) ["Sleepy", "Grumpy", "Happy", "Bashful", "Sneezy", "Doc", "Dopey"]
Collapse
stevebrownlee profile image
Steve Brownlee Author

Thanks, Kushan. I did realize that the iterator could be simplified about a month after I wrote the original article, but never updated it, so thanks for posting what that looks like. Great stuff.