JavaScript 迭代器协议深入:从 Symbol.iterator 到异步生成器
前言
迭代(Iteration)是编程中最基础的操作之一。JavaScript 通过**迭代器协议(Iterator Protocol)和可迭代协议(Iterable Protocol)**定义了一套统一的迭代标准,让 for...of、展开运算符、解构赋值等语法特性能够与任何自定义数据结构无缝协作。
本文将从协议的底层原理讲起,逐步深入到生成器、惰性求值、异步迭代器和 for-await-of,帮你全面掌握 JavaScript 迭代体系。
迭代器协议(Iterator Protocol)
一个对象要成为迭代器,必须实现一个 next() 方法,该方法返回一个包含 value 和 done 属性的对象:
// 手动实现一个迭代器function createRangeIterator(start, end) { let current = start; return { next() { if (current <= end) { return { value: current++, done: false }; } return { value: undefined, done: true }; } };}
const iter = createRangeIterator(1, 3);console.log(iter.next()); // { value: 1, done: false }console.log(iter.next()); // { value: 2, done: false }console.log(iter.next()); // { value: 3, done: false }console.log(iter.next()); // { value: undefined, done: true }迭代器是有状态的——每次调用 next() 都会推进内部状态。一旦 done: true,后续调用应持续返回 { done: true }。
可迭代协议(Iterable Protocol)
一个对象要成为可迭代的,必须实现 Symbol.iterator 方法,该方法返回一个迭代器:
class Range { constructor(start, end) { this.start = start; this.end = end; }
[Symbol.iterator]() { let current = this.start; const end = this.end;
return { next() { if (current <= end) { return { value: current++, done: false }; } return { value: undefined, done: true }; } }; }}
const range = new Range(1, 5);
// for...of 自动调用 Symbol.iteratorfor (const num of range) { console.log(num); // 1, 2, 3, 4, 5}
// 展开运算符console.log([...range]); // [1, 2, 3, 4, 5]
// 解构赋值const [first, second, ...rest] = range;console.log(first, second, rest); // 1 2 [3, 4, 5]
// Array.fromconsole.log(Array.from(range)); // [1, 2, 3, 4, 5]可迭代协议的消费者
以下语法和 API 都依赖可迭代协议:
| 消费者 | 示例 |
|---|---|
for...of | for (const x of iterable) |
| 展开运算符 | [...iterable], fn(...iterable) |
| 解构赋值 | const [a, b] = iterable |
Array.from() | Array.from(iterable) |
new Map() / new Set() | new Set(iterable) |
Promise.all() | Promise.all(iterable) |
yield* | yield* iterable |
生成器函数(Generator Functions)
手动实现迭代器比较繁琐,生成器函数(function*)提供了更优雅的方式:
function* range(start, end) { for (let i = start; i <= end; i++) { yield i; }}
for (const num of range(1, 5)) { console.log(num); // 1, 2, 3, 4, 5}生成器函数返回的对象同时满足迭代器协议和可迭代协议:
const gen = range(1, 3);
// 它是一个迭代器(有 next 方法)console.log(gen.next()); // { value: 1, done: false }
// 它也是可迭代的(有 Symbol.iterator 方法)console.log(gen[Symbol.iterator]() === gen); // trueyield* 委托
yield* 可以将迭代委托给另一个可迭代对象:
function* concat(...iterables) { for (const iterable of iterables) { yield* iterable; }}
console.log([...concat([1, 2], [3, 4], [5])]); // [1, 2, 3, 4, 5]
// 递归生成器——遍历树结构function* traverseTree(node) { yield node.value; if (node.children) { for (const child of node.children) { yield* traverseTree(child); } }}
const tree = { value: 1, children: [ { value: 2, children: [{ value: 4 }, { value: 5 }] }, { value: 3, children: [{ value: 6 }] } ]};
console.log([...traverseTree(tree)]); // [1, 2, 4, 5, 3, 6]双向通信:next() 传值与 return/throw
生成器不仅仅是”产出值”,它还支持双向通信:
function* calculator() { let result = 0; while (true) { const input = yield result; if (input === null) break; result += input; } return result; // return 值出现在最后的 { value, done: true } 中}
const calc = calculator();console.log(calc.next()); // { value: 0, done: false } — 启动生成器console.log(calc.next(10)); // { value: 10, done: false }console.log(calc.next(20)); // { value: 30, done: false }console.log(calc.next(5)); // { value: 35, done: false }console.log(calc.next(null)); // { value: 35, done: true }return() 和 throw() 方法用于从外部控制生成器:
function* gen() { try { yield 1; yield 2; yield 3; } finally { console.log('cleanup'); }}
const g = gen();console.log(g.next()); // { value: 1, done: false }console.log(g.return(42)); // cleanup → { value: 42, done: true }
const g2 = gen();g2.next();g2.throw(new Error('boom')); // cleanup → Error: boom惰性求值(Lazy Evaluation)
迭代器的一个核心优势是惰性求值——值只在需要时才被计算,不会预先创建整个集合。
无限序列
function* naturals() { let n = 1; while (true) { yield n++; }}
function* fibonacci() { let [a, b] = [0, 1]; while (true) { yield a; [a, b] = [b, a + b]; }}
// 取前 10 个斐波那契数function take(n, iterable) { const result = []; for (const value of iterable) { result.push(value); if (result.length >= n) break; } return result;}
console.log(take(10, fibonacci())); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]惰性管道操作
通过组合生成器,可以构建高效的数据处理管道:
function* map(fn, iterable) { for (const value of iterable) { yield fn(value); }}
function* filter(predicate, iterable) { for (const value of iterable) { if (predicate(value)) { yield value; } }}
function* takeWhile(predicate, iterable) { for (const value of iterable) { if (!predicate(value)) break; yield value; }}
function* flatMap(fn, iterable) { for (const value of iterable) { yield* fn(value); }}
// 组合使用:找出前 5 个大于 100 的斐波那契数const result = take(5, filter( n => n > 100, fibonacci() ));
console.log(result); // [144, 233, 377, 610, 987]与数组方法的区别在于:惰性管道不会创建中间数组,每个元素”流过”整条管道后才处理下一个。
构建流式 API
class LazySequence { #source;
constructor(source) { this.#source = source; }
static from(iterable) { return new LazySequence(iterable); }
static range(start, end) { return new LazySequence(function* () { for (let i = start; i <= end; i++) yield i; }()); }
static infinite(fn) { return new LazySequence(function* () { let i = 0; while (true) yield fn(i++); }()); }
map(fn) { const source = this.#source; return new LazySequence(function* () { for (const value of source) { yield fn(value); } }()); }
filter(predicate) { const source = this.#source; return new LazySequence(function* () { for (const value of source) { if (predicate(value)) yield value; } }()); }
take(n) { const source = this.#source; return new LazySequence(function* () { let count = 0; for (const value of source) { if (count++ >= n) break; yield value; } }()); }
reduce(fn, initial) { let acc = initial; for (const value of this.#source) { acc = fn(acc, value); } return acc; }
toArray() { return [...this.#source]; }
[Symbol.iterator]() { return this.#source[Symbol.iterator](); }}
// 使用const result2 = LazySequence .range(1, 1_000_000) .filter(n => n % 2 === 0) .map(n => n * n) .take(5) .toArray();
console.log(result2); // [4, 16, 36, 64, 100]// 只计算了 10 个数字,而不是 100 万个异步迭代器(Async Iterator)
当数据源是异步的(网络请求、文件读取、事件流),同步迭代器就不够用了。ES2018 引入了异步迭代器协议。
异步迭代器协议
// 异步可迭代对象需要实现 Symbol.asyncIteratorconst asyncRange = { from: 1, to: 5,
[Symbol.asyncIterator]() { let current = this.from; const last = this.to;
return { async next() { // 模拟异步操作 await new Promise(resolve => setTimeout(resolve, 100));
if (current <= last) { return { value: current++, done: false }; } return { value: undefined, done: true }; } }; }};
// 使用 for-await-of 消费async function main() { for await (const num of asyncRange) { console.log(num); // 1, 2, 3, 4, 5(每个间隔 100ms) }}
main();异步生成器(Async Generator)
就像同步生成器简化了迭代器实现,异步生成器简化了异步迭代器:
async function* fetchPages(baseUrl, maxPages = 10) { let page = 1; let hasMore = true;
while (hasMore && page <= maxPages) { const response = await fetch(`${baseUrl}?page=${page}`); const data = await response.json();
yield data.items;
hasMore = data.hasNextPage; page++; }}
// 消费分页 APIasync function getAllItems() { const allItems = [];
for await (const items of fetchPages('https://api.example.com/posts')) { allItems.push(...items); console.log(`Fetched ${items.length} items, total: ${allItems.length}`); }
return allItems;}实战:事件流转异步迭代器
将 EventEmitter 或 DOM 事件转换为异步迭代器是一个非常实用的模式:
function eventToAsyncIterator(emitter, eventName) { const queue = []; let resolve = null; let done = false;
emitter.addEventListener(eventName, (event) => { if (resolve) { resolve({ value: event, done: false }); resolve = null; } else { queue.push(event); } });
return { [Symbol.asyncIterator]() { return this; },
next() { if (queue.length > 0) { return Promise.resolve({ value: queue.shift(), done: false }); } if (done) { return Promise.resolve({ value: undefined, done: true }); } return new Promise(r => { resolve = r; }); },
return() { done = true; return Promise.resolve({ value: undefined, done: true }); } };}
// 使用示例async function handleClicks() { const clicks = eventToAsyncIterator(document, 'click');
for await (const event of clicks) { console.log(`Clicked at (${event.clientX}, ${event.clientY})`); }}异步生成器实战:流式读取大文件
async function* readFileChunks(filePath, chunkSize = 64 * 1024) { const fs = await import('fs'); const fh = await fs.promises.open(filePath, 'r');
try { const buffer = Buffer.alloc(chunkSize); let bytesRead;
do { const result = await fh.read(buffer, 0, chunkSize); bytesRead = result.bytesRead;
if (bytesRead > 0) { yield buffer.subarray(0, bytesRead); } } while (bytesRead === chunkSize); } finally { await fh.close(); }}
async function* readLines(filePath) { let leftover = '';
for await (const chunk of readFileChunks(filePath)) { const text = leftover + chunk.toString('utf-8'); const lines = text.split('\n'); leftover = lines.pop() || '';
for (const line of lines) { yield line; } }
if (leftover) { yield leftover; }}
// 使用async function processLog() { let lineCount = 0; let errorCount = 0;
for await (const line of readLines('/var/log/app.log')) { lineCount++; if (line.includes('ERROR')) { errorCount++; console.log(`Line ${lineCount}: ${line}`); } }
console.log(`Total: ${lineCount} lines, ${errorCount} errors`);}异步管道操作
async function* asyncMap(fn, asyncIterable) { for await (const value of asyncIterable) { yield await fn(value); }}
async function* asyncFilter(predicate, asyncIterable) { for await (const value of asyncIterable) { if (await predicate(value)) { yield value; } }}
async function* asyncTake(n, asyncIterable) { let count = 0; for await (const value of asyncIterable) { if (count++ >= n) break; yield value; }}
async function* asyncBatch(size, asyncIterable) { let batch = []; for await (const value of asyncIterable) { batch.push(value); if (batch.length >= size) { yield batch; batch = []; } } if (batch.length > 0) { yield batch; }}
// 组合使用:批量处理 API 数据async function processAPIData() { const pages = fetchPages('https://api.example.com/users');
const pipeline = asyncBatch(10, asyncFilter( async user => user.active, asyncMap( async items => items.flat(), pages ) ) );
for await (const batch of pipeline) { console.log(`Processing batch of ${batch.length} users`); await saveBatch(batch); }}Iterator Helpers(提案 Stage 3)
TC39 的 Iterator Helpers 提案为迭代器添加了内置的链式操作方法,不再需要手动编写工具函数:
// Iterator Helpers — 现代浏览器已开始支持const result3 = Iterator.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) .filter(n => n % 2 === 0) .map(n => n ** 2) .take(3) .toArray();
console.log(result3); // [4, 16, 36]
// 无限序列也能安全使用function* primes() { const seen = []; let n = 2; while (true) { if (seen.every(p => n % p !== 0)) { seen.push(n); yield n; } n++; }}
// 前 10 个质数const first10Primes = Iterator.from(primes()) .take(10) .toArray();
console.log(first10Primes); // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
// reduceconst sum = Iterator.from(naturals()) .take(100) .reduce((acc, n) => acc + n, 0);
console.log(sum); // 5050
// forEachIterator.from([1, 2, 3]) .map(n => n * 10) .forEach(n => console.log(n)); // 10, 20, 30
// some / every / findconst hasEven = Iterator.from([1, 3, 4, 7]) .some(n => n % 2 === 0);console.log(hasEven); // true自定义可迭代数据结构
链表
class LinkedList { #head = null; #size = 0;
append(value) { const node = { value, next: null }; if (!this.#head) { this.#head = node; } else { let current = this.#head; while (current.next) current = current.next; current.next = node; } this.#size++; return this; }
get size() { return this.#size; }
*[Symbol.iterator]() { let current = this.#head; while (current) { yield current.value; current = current.next; } }
// 反向迭代 *reversed() { const values = [...this]; for (let i = values.length - 1; i >= 0; i--) { yield values[i]; } }}
const list = new LinkedList();list.append(1).append(2).append(3);
for (const value of list) { console.log(value); // 1, 2, 3}
console.log([...list.reversed()]); // [3, 2, 1]二叉搜索树
class BST { #root = null;
insert(value) { const node = { value, left: null, right: null }; if (!this.#root) { this.#root = node; return this; }
let current = this.#root; while (true) { if (value < current.value) { if (!current.left) { current.left = node; break; } current = current.left; } else { if (!current.right) { current.right = node; break; } current = current.right; } } return this; }
// 中序遍历 — 自动排序输出 *[Symbol.iterator]() { yield* this.#inorder(this.#root); }
*#inorder(node) { if (!node) return; yield* this.#inorder(node.left); yield node.value; yield* this.#inorder(node.right); }
// 层序遍历 *bfs() { if (!this.#root) return; const queue = [this.#root];
while (queue.length) { const node = queue.shift(); yield node.value; if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } }}
const tree2 = new BST();tree2.insert(5).insert(3).insert(7).insert(1).insert(4).insert(6).insert(8);
console.log([...tree2]); // [1, 3, 4, 5, 6, 7, 8] — 中序console.log([...tree2.bfs()]); // [5, 3, 7, 1, 4, 6, 8] — 层序性能考量
- 生成器的开销:生成器函数每次调用
next()都涉及上下文切换,对于简单的数值循环,for循环比for...of+ 生成器快 2-5 倍 - 内存优势:当处理大量数据时,惰性迭代器的内存优势远大于性能开销。100 万条数据的数组链式操作会创建多个中间数组,而迭代器管道几乎不占额外内存
- 异步迭代器的微任务开销:每次
await都会产生微任务,在高频场景下需要注意。可以通过批处理(batching)减少await次数
总结
JavaScript 的迭代器体系是一个精心设计的抽象层:
- Iterator Protocol 定义了”如何逐个产出值”
- Iterable Protocol 定义了”如何获取迭代器”
- Generator 简化了迭代器的实现
- Async Iterator / Generator 将迭代扩展到异步领域
- Iterator Helpers 提供了开箱即用的函数式操作
理解并熟练运用这些协议,不仅能写出更优雅的代码,还能在处理大数据集、流式数据、异步事件流等场景下获得显著的性能和内存优势。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!