Iterators and Iterables in Javascript
Although iterators and iterables are important concepts in JavaScript, they may not be as widely discussed or well-known as some other features of the language. They constitute the core fundamentals of JavaScript and are used in various scenarios, especially when working with collections and performing iterations.
While not as famous as some other JavaScript concepts like functions or objects, iterators are an essential tool for working with collections in JavaScript, and understanding their usage can greatly enhance your ability to manipulate and iterate over data structures.
In this blog we will cover:
- Iterators
- Custom iterators
- Iterables
- Custom iterables
- Iterators vs iterables
What is an iterator?
Iterators are handy tools in JavaScript, especially when paired with the “for...of loop”. They allow us to iterate over elements in a clean and straightforward way. You may have encountered them when using built-in functions like “Array.prototype.map()” or “Array.prototype.forEach()” to work with collections.
Long story short, think of iterators as a closure that remembers its current state and moves to the next element when we request it. It's a powerful concept that opens up a whole new world of possibilities for flexible and controlled looping.
Building blocks of an iterator:
- next(): Next function returns an object consisting of two keys {value , done}
- {value, done}: Each next execution returns a value but done is returned as false until the iteration is complete (reaches length of the array/ object). Once all the values are iterated, they are returned as true and value turns out to be undefined
- Symbol.iterator: This symbol is a well-known symbol in JavaScript that defines the default iterator for an object. It is typically implemented as a function that returns the iterator object itself
To use an iterator, you typically call the “Symbol.iterator” method on a collection or data structure to obtain an iterator object. Let’s use an iterator over an array:
var temp = [1,2,3]
- Fetch the iterator object for the collection or data structure (array in this case)
var iterator = temp[Symbol.iterator]
- Use .next() on the iterator to fetch the values:
iterator.next(); // returns { value: 1, done: false }
iterator.next(); // returns { value: 2, done: false }
iterator.next(); // returns{ value: 3, done: false }
iterator.next(); // returns { value: undefined, done: true }
…and that’s it. Simple, wasn’t it?
What about a custom iterator?
As we discussed earlier, an iterator is a closure which returns an object having function next()/ or function next(), on each subsequent calling of the next function we get the next iteration value.
If it looks like too much code, simplify with generators.
Generators basically auto-provide the next function, so the dev should not be worried about returning a closure (next()) and maintaining internal state references.
What is an iterable?
An iterable is an object that defines the iterator property and to be iterable, an object must implement the `Symbol.iterator` method. This property converts the object into an iterable. For iterables that can only be iterated once, such as generators, they typically return the object itself from their `@@iterator` method. However, for iterables that can be iterated multiple times, a new iterator must be returned with each invocation of `@@iterator`.
What about custom iterables or user-defined iterables?
It’s best explained by seeing it in action. So, let’s code!
Below, we are writing an iterable object which will iterate over an array and print the array values.
Iterators vs iterables
As discussed above, an iterable is an object comprising an iterator—which is defined under the key Symbol.iterator. The iterator serves as the action definition for iteration, containing custom logic for the next() function. The next() function is called iteratively, which returns a value and done keys as per protocol, providing information about the current element being processed and whether the iteration has reached its end.
In conclusion, iterators and iterables are fundamental concepts in JavaScript that facilitate the traversal and manipulation of collections. Iterators provide a standardized way to access elements sequentially, while iterables are objects that define the iteration behavior by implementing the iterator protocol.
By creating custom iterators and iterables, developers can extend the functionality of JavaScript to suit their specific needs. They offer a gateway to unleash custom logic and behaviors when accessing elements, providing a whole new level of flexibility. Custom iterators grant precise control over the iteration process, while custom iterables offer a higher-level interface for seamless integration with built-in language constructs and functions. With the ability to create these custom constructs, developers can truly make JavaScript their own and unlock endless possibilities for innovation and efficiency.
Understanding iterators and iterables, along with generators, unlocks the ability to write efficient, modular, and reusable code when dealing with collections and performing iterations. Whether you’re leveraging built-in iterables or creating your own custom ones, these concepts are essential tools in a JavaScript developer’s toolkit. Although we discussed iterators and iterables in this article, it's crucial to note that we haven't explored another powerful concept in JavaScript: Generators. Generators are a special function syntax that offers even greater flexibility and control over the iteration process. Stay tuned for our upcoming article on generators to further expand your knowledge and proficiency in JavaScript iteration techniques.