8. Arrays and iteration: methods, iterators, generators
Arrays are objects with numeric keys and a length that auto-updates. The
functional methods (map/filter/reduce) don't mutate; a handful of
older methods (push, splice, sort, reverse) do — know which is which,
since mutating a shared array is a classic bug source in UI state.
const nums = [1, 2, 3, 4, 5];
nums.map(n => n * 2); // [2,4,6,8,10] — new array
nums.filter(n => n % 2 === 0); // [2,4] — new array
nums.reduce((sum, n) => sum + n, 0); // 15 — folds to one value
nums.sort((a, b) => b - a); // MUTATES nums in place, now [5,4,3,2,1]
The iterator protocol is what makes for...of, spread (...), and
destructuring work on arrays, Map, Set, strings, and any custom object
that implements Symbol.iterator:
class Range {
constructor(start, end) { this.start = start; this.end = end; }
[Symbol.iterator]() {
let current = this.start, end = this.end;
return {
next() {
return current <= end
? { value: current++, done: false }
: { value: undefined, done: true };
},
};
}
}
[...new Range(1, 5)]; // [1,2,3,4,5] — works because Range implements the protocol
for (const n of new Range(1, 3)) console.log(n); // 1, 2, 3
Generators (function*) are a shortcut for writing iterators without
hand-rolling next():
function* range(start, end) {
for (let i = start; i <= end; i++) yield i;
}
[...range(1, 5)]; // [1,2,3,4,5]
Generators also underpin how async iteration and (historically) some
coroutine-style async patterns work — yield pauses execution and hands a
value out, exactly like await pauses on a promise (§14).