JavaScript 迭代器协议深入:从 Symbol.iterator 到异步生成器

2980 字
15 分钟
JavaScript 迭代器协议深入:从 Symbol.iterator 到异步生成器

前言#

迭代(Iteration)是编程中最基础的操作之一。JavaScript 通过**迭代器协议(Iterator Protocol)可迭代协议(Iterable Protocol)**定义了一套统一的迭代标准,让 for...of、展开运算符、解构赋值等语法特性能够与任何自定义数据结构无缝协作。

本文将从协议的底层原理讲起,逐步深入到生成器、惰性求值、异步迭代器和 for-await-of,帮你全面掌握 JavaScript 迭代体系。

迭代器协议(Iterator Protocol)#

一个对象要成为迭代器,必须实现一个 next() 方法,该方法返回一个包含 valuedone 属性的对象:

// 手动实现一个迭代器
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.iterator
for (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.from
console.log(Array.from(range)); // [1, 2, 3, 4, 5]

可迭代协议的消费者#

以下语法和 API 都依赖可迭代协议:

消费者示例
for...offor (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); // true

yield* 委托#

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.asyncIterator
const 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++;
}
}
// 消费分页 API
async 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]
// reduce
const sum = Iterator.from(naturals())
.take(100)
.reduce((acc, n) => acc + n, 0);
console.log(sum); // 5050
// forEach
Iterator.from([1, 2, 3])
.map(n => n * 10)
.forEach(n => console.log(n)); // 10, 20, 30
// some / every / find
const 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] — 层序

性能考量#

  1. 生成器的开销:生成器函数每次调用 next() 都涉及上下文切换,对于简单的数值循环,for 循环比 for...of + 生成器快 2-5 倍
  2. 内存优势:当处理大量数据时,惰性迭代器的内存优势远大于性能开销。100 万条数据的数组链式操作会创建多个中间数组,而迭代器管道几乎不占额外内存
  3. 异步迭代器的微任务开销:每次 await 都会产生微任务,在高频场景下需要注意。可以通过批处理(batching)减少 await 次数

总结#

JavaScript 的迭代器体系是一个精心设计的抽象层:

  • Iterator Protocol 定义了”如何逐个产出值”
  • Iterable Protocol 定义了”如何获取迭代器”
  • Generator 简化了迭代器的实现
  • Async Iterator / Generator 将迭代扩展到异步领域
  • Iterator Helpers 提供了开箱即用的函数式操作

理解并熟练运用这些协议,不仅能写出更优雅的代码,还能在处理大数据集、流式数据、异步事件流等场景下获得显著的性能和内存优势。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

JavaScript 迭代器协议深入:从 Symbol.iterator 到异步生成器
https://boke.hackerdream.xyz/posts/javascript-iterator-protocol/
作者
晴天
发布于
2026-01-06
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
晴天
Hello, I'm 晴天.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
125
分类
17
标签
287
总字数
257,955
运行时长
0
最后活动
0 天前

目录