💡 Key Takeaways
🔁 Core Concept: Iterators define how to access items from a collection one by one.
⚙️ Protocol Power: Every iterator must have a .next() method returning { value, done }.
⚡ Generator Shortcut: Generator functions (function*) are an easy way to make iterators.
🔍 What’s an Iterator?
- An iterator is an object with a
.next()method. - Each call to
.next()returns{ value, done }. - Used behind the scenes by
for...of, spread, and destructuring.
const arr = [10, 20, 30];
const iterator = arr[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 }
🧱 The Iteration Protocol
- An object is iterable if it defines
Symbol.iterator(). - That method must return an iterator with
.next(). - Lets you make objects usable in loops like arrays.
const iterableObj = {
data: [1, 2, 3],
[Symbol.iterator]() {
let i = 0;
return {
next: () => ({
value: this.data[i],
done: i++ >= this.data.length
})
};
}
};
for (const num of iterableObj) console.log(num);
// 1, 2, 3
🧭 Iterating Built-in Objects
- Arrays, strings, Maps, and Sets are already iterable.
for...ofworks on any built-in iterable.- You can also call their iterator manually if needed.
const str = "Hi!";
for (const char of str) console.log(char);
// H, i, !
const set = new Set([1, 2]);
for (const val of set) console.log(val);
// 1, 2
const map = new Map([['a', 1], ['b', 2]]);
for (const [k, v] of map) console.log(k, v);
// a 1, b 2
✨ Using the Spread Operator
- Spread (
...) works on any iterable. - It converts iterables into arrays or function arguments.
- Great for cloning, merging, or logging sequences.
const arr = [1, 2, 3];
console.log([...arr]); // [1, 2, 3]
const str = "abc";
console.log([...str]); // ['a', 'b', 'c']
const unique = new Set([1, 2, 2]);
console.log([...unique]); // [1, 2]
⚙️ Custom Iterator Example
- You can manually define
.next()for full control. - Useful for generating custom sequences or counters.
- Returns
{ value, done }until finished.
function makeCounter(limit) {
let count = 0;
return {
next() {
if (count < limit) return { value: count++, done: false };
return { done: true };
}
};
}
const counter = makeCounter(3);
console.log(counter.next()); // { value: 0, done: false }
console.log(counter.next()); // { value: 1, done: false }
console.log(counter.next()); // { value: 2, done: false }
console.log(counter.next()); // { done: true }
⚡ Generators: The Easy Iterators
- Generator functions (
function*) create iterators automatically. yieldpauses execution and produces a value.- You can even send data back in with
.next(value).
function* greet() {
const name = yield "What’s your name?";
yield `Hello, ${name}!`;
}
const g = greet();
console.log(g.next().value); // "What’s your name?"
console.log(g.next("Ada").value); // "Hello, Ada!"
🧩 Combining Iterables
- Spread syntax merges multiple iterables easily.
- Generators can delegate iteration using
yield*. - Combine arrays, strings, or any iterable seamlessly.
const nums = [1, 2];
const letters = ['a', 'b'];
const merged = [...nums, ...letters];
console.log(merged); // [1, 2, 'a', 'b']
function* combine() {
yield* [1, 2];
yield* ['a', 'b'];
}
console.log([...combine()]); // [1, 2, 'a', 'b']