What Are Iterables?
Iterables are objects that implement the @@iterator method, making their data accessible in a sequential manner. These objects conform to the Iterable Protocol, which defines how elements within the object can be accessed and iterated over.
Common examples of iterable objects in JavaScript:
- Arrays
- Strings
- Maps
- Sets
- Typed arrays (e.g., Int32Array)
- Custom objects implementing the iterable protocol
How Iterables Work
Iterables use an internal method called Symbol.iterator to produce an iterator object. The iterator object has a next() method that returns each element of the iterable, wrapped in an object with two properties:
- value: The current element.
- done: A boolean indicating whether the iteration is complete (true) or not (false).
Example of Iteration
Here’s how iteration works under the hood:
const iterable = [10, 20, 30];
// Retrieve the iterator
const iterator = iterable[Symbol.iterator]();
console.log(iterator.next()); // { value: 10, done: false }
console.log(iterator.next()); // { value: 20, done: false }
console.log(iterator.next()); // { value: 30, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
Explanation:
- The Symbol.iterator method returns an iterator for the array.
- The next() method fetches the next element until done becomes true.
Using Iterables with for…of
The for…of loop provides a convenient way to iterate over all elements of an iterable object.
const numbers = [1, 2, 3, 4, 5];
for (const num of numbers) {
console.log(num);
}
// Output:
// 1
// 2
// 3
// 4
// 5
Why use for…of?
- It is simpler and more readable than manually calling next().
- It works with any iterable object.
Iterables in Strings
Strings are also iterable, meaning you can loop through their characters one by one.
const word = "Hello";
for (const char of word) {
console.log(char);
}
// Output:
// H
// e
// l
// l
// o
Iterables in Maps and Sets
Maps and Sets, introduced in ES6, are also iterable objects.
Example: Iterating Through a Map
const map = new Map([
['a', 1],
['b', 2],
['c', 3]
]);
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
// Output:
// a: 1
// b: 2
// c: 3
Example: Iterating Through a Set
const set = new Set([10, 20, 30, 40]);
for (const value of set) {
console.log(value);
}
// Output:
// 10
// 20
// 30
// 40
Custom Iterables
You can create your own iterable objects by implementing the Symbol.iterator method.
Example: Custom Iterable Object
const customIterable = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
for (const value of customIterable) {
console.log(value);
}
// Output:
// 1
// 2
// 3
Explanation:
- The Symbol.iterator method creates an iterator for the object.
- The next() method specifies how to retrieve the next element.
Practical Use Cases
- Processing Data: Iterate through arrays, maps, or sets to process data.
- String Manipulation: Loop through strings for operations like character counting.
- Custom Data Structures: Create custom iterable objects to represent data structures.
- Asynchronous Iteration: Use iterables with asynchronous generators for handling promises.
Best Practices
- Use Built-In Iterables: Leverage arrays, maps and sets for most tasks.
- Simplify Loops with for…of: Avoid manual iteration unless necessary.
- Define Meaningful Iterators: When creating custom iterables, ensure next() behaves logically.
Common Mistakes
Using for…in Instead of for…of:
- for…in iterates over object properties, not elements of an iterable.
const arr = [10, 20, 30];
for (const key in arr) {
console.log(key); // Outputs: 0, 1, 2 (indexes)
}
Skipping Symbol.iterator: Custom objects must explicitly implement Symbol.iterator to be iterable.