我将为您完善迭代协议的文档,详细介绍迭代协议的概念、用法和应用场景。
---
title: 迭代协议
icon: javascript
order: 1
---
# 迭代协议
迭代协议定义了对象如何实现可迭代行为。本文将介绍可迭代协议和迭代器协议的概念、Symbol.iterator的作用,以及如何创建自定义可迭代对象。
## 迭代协议概述
JavaScript 的迭代协议由两个协议组成:**可迭代协议**和**迭代器协议**。这些协议不是新的内置对象或语法,而是协议(或者说是接口)。通过实现这些协议,对象可以在 `for...of` 循环、展开运算符、解构赋值等语法中使用。
## 可迭代协议(Iterable Protocol)
可迭代协议允许 JavaScript 对象定义或自定义它们的迭代行为。要成为可迭代对象,该对象必须实现 `@@iterator` 方法,这意味着对象(或其原型链上的某个对象)必须有一个键为 `Symbol.iterator` 的属性,该属性的值是一个无参数函数,返回一个符合迭代器协议的对象。
### 内置可迭代对象
JavaScript 中许多内置类型都实现了可迭代协议:
- `String`
- `Array`
- `TypedArray`
- `Map`
- `Set`
- 函数的 `arguments` 对象
- `NodeList` 等 DOM 集合类型
### 可迭代对象的使用场景
可迭代对象可以在以下语法结构中使用:
```javascript
// for...of 循环
for (const item of iterable) {
console.log(item);
}
// 展开运算符
const arr = [...iterable];
// 解构赋值
const [a, b, c] = iterable;
// Promise.all
Promise.all(iterable);
// Array.from
const array = Array.from(iterable);
// Map 和 Set 构造函数
new Map(iterable);
new Set(iterable);
迭代器协议(Iterator Protocol)
迭代器协议定义了产生一系列值(有限或无限)的标准方式。当一个对象实现了 next()
方法,并且该方法返回包含 done
和 value
属性的对象时,该对象就符合迭代器协议。
next() 方法
迭代器的 next()
方法返回的对象应该有以下属性:
done
(布尔值)- 如果迭代器已经完成迭代序列,则为
true
- 如果迭代器能够产生序列中的下一个值,则为
false
- 如果迭代器已经完成迭代序列,则为
value
- 迭代器返回的任何 JavaScript 值
- 当
done
为true
时可以省略
迭代器示例
// 一个简单的迭代器
function createIterator(array) {
let index = 0;
return {
next: function() {
if (index < array.length) {
return { value: array[index++], done: false };
} else {
return { done: true };
}
}
};
}
const iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { done: true }
Symbol.iterator
Symbol.iterator
是一个内置的 Symbol 值,用于指定对象的默认迭代器。当一个对象需要被迭代时(例如在 for...of
循环中),它的 Symbol.iterator
方法会被调用,并且返回一个迭代器。
使用 Symbol.iterator 创建可迭代对象
const myIterable = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next: function() {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
return { done: true };
}
}
};
}
};
// 现在可以使用 for...of 循环了
for (const item of myIterable) {
console.log(item); // 输出: 1, 2, 3
}
// 也可以使用展开运算符
const arr = [...myIterable]; // [1, 2, 3]
自定义可迭代对象
基本示例
// 创建一个范围对象,可以迭代指定范围内的数字
function createRange(start, end, step = 1) {
return {
[Symbol.iterator]() {
let current = start;
return {
next() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
} else {
return { done: true };
}
}
};
}
};
}
const range = createRange(1, 10, 2);
for (const num of range) {
console.log(num); // 输出: 1, 3, 5, 7, 9
}
无限迭代器
迭代器可以产生无限序列,但在使用时需要小心避免无限循环:
// 创建一个斐波那契数列迭代器
function fibonacciIterator() {
let prev = 0, curr = 1;
return {
[Symbol.iterator]() {
return this;
},
next() {
const value = curr;
curr = prev + curr;
prev = value;
return { value, done: false }; // 永远不会结束
}
};
}
const fib = fibonacciIterator();
let count = 0;
// 使用 for...of 时需要手动中断,否则会无限循环
for (const num of fib) {
console.log(num);
if (++count >= 10) break; // 只获取前 10 个数
}
可迭代对象与迭代器对象合并
有时,迭代器本身也可以是可迭代的,这样可以简化代码:
function createIterableIterator(array) {
let index = 0;
const iterator = {
next() {
if (index < array.length) {
return { value: array[index++], done: false };
} else {
return { done: true };
}
},
// 使迭代器本身可迭代
[Symbol.iterator]() {
return this;
}
};
return iterator;
}
const iterableIterator = createIterableIterator(['a', 'b', 'c']);
// 作为迭代器使用
console.log(iterableIterator.next()); // { value: 'a', done: false }
console.log(iterableIterator.next()); // { value: 'b', done: false }
// 重置迭代器
const sameIterableIterator = iterableIterator[Symbol.iterator]();
// 作为可迭代对象使用
for (const item of sameIterableIterator) {
console.log(item); // 输出: 'c'(因为前面已经消费了 'a' 和 'b')
}
迭代器的高级特性
可关闭的迭代器
迭代器可以实现一个可选的 return()
方法,当迭代提前退出时(例如通过 break
、return
或异常),该方法会被调用,用于执行清理工作:
function createCloseableIterator() {
let isOpen = true;
let resource = acquireResource(); // 假设这是一个需要释放的资源
return {
[Symbol.iterator]() {
return this;
},
next() {
if (!isOpen) {
return { done: true };
}
// 使用资源生成下一个值
const value = generateNextValue(resource);
if (shouldStop(value)) {
this.return();
return { done: true };
}
return { value, done: false };
},
return() {
if (isOpen) {
console.log('清理资源');
releaseResource(resource); // 释放资源
isOpen = false;
}
return { done: true };
}
};
}
// 使用示例
const iterator = createCloseableIterator();
try {
for (const item of iterator) {
console.log(item);
if (someCondition(item)) {
break; // 提前退出循环,会调用 return()
}
}
} catch (error) {
// 即使发生异常,return() 也会被调用
}
可抛出的迭代器
迭代器还可以实现一个可选的 throw()
方法,用于向迭代器传递错误:
function createThrowableIterator() {
let count = 0;
return {
[Symbol.iterator]() {
return this;
},
next() {
count++;
if (count <= 3) {
return { value: count, done: false };
} else {
return { done: true };
}
},
throw(error) {
console.log('捕获到错误:', error.message);
count = 4; // 强制迭代结束
return { done: true };
}
};
}
const iterator = createThrowableIterator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.throw(new Error('测试错误'))); // 输出: 捕获到错误: 测试错误,返回 { done: true }
console.log(iterator.next()); // { done: true }
实际应用场景
分页数据迭代
// 模拟分页 API
async function fetchPage(pageNum) {
// 假设这是一个 API 调用
return {
data: Array.from({ length: 10 }, (_, i) => pageNum * 10 + i + 1),
hasNextPage: pageNum < 5 // 总共 5 页
};
}
// 创建分页数据迭代器
function createPaginationIterator() {
let currentPage = 0;
let currentIndex = 0;
let currentData = [];
let hasNextPage = true;
return {
[Symbol.asyncIterator]() {
return this;
},
async next() {
// 如果当前页的数据已经用完,并且还有下一页,则获取下一页
if (currentIndex >= currentData.length && hasNextPage) {
const response = await fetchPage(currentPage++);
currentData = response.data;
hasNextPage = response.hasNextPage;
currentIndex = 0;
}
// 如果还有数据,返回下一个元素
if (currentIndex < currentData.length) {
return { value: currentData[currentIndex++], done: false };
} else {
return { done: true };
}
}
};
}
// 使用异步迭代器
async function processData() {
const iterator = createPaginationIterator();
for await (const item of iterator) {
console.log(item);
}
}
processData();
树结构遍历
// 定义树节点
class TreeNode {
constructor(value, children = []) {
this.value = value;
this.children = children;
}
// 深度优先遍历
*[Symbol.iterator]() {
yield this.value;
for (const child of this.children) {
yield* child; // 委托给子节点的迭代器
}
}
}
// 创建一个树
const tree = new TreeNode(1, [
new TreeNode(2, [
new TreeNode(4),
new TreeNode(5)
]),
new TreeNode(3, [
new TreeNode(6),
new TreeNode(7)
])
]);
// 遍历树
for (const value of tree) {
console.log(value); // 输出: 1, 2, 4, 5, 3, 6, 7
}
自定义集合类
class Collection {
constructor(items = []) {
this.items = items;
}
add(item) {
this.items.push(item);
return this;
}
remove(item) {
const index = this.items.indexOf(item);
if (index !== -1) {
this.items.splice(index, 1);
}
return this;
}
// 实现可迭代协议
[Symbol.iterator]() {
let index = 0;
const items = this.items;
return {
next() {
if (index < items.length) {
return { value: items[index++], done: false };
} else {
return { done: true };
}
}
};
}
// 添加一些常用的迭代方法
map(callback) {
const result = new Collection();
for (const item of this) {
result.add(callback(item));
}
return result;
}
filter(callback) {
const result = new Collection();
for (const item of this) {
if (callback(item)) {
result.add(item);
}
}
return result;
}
toArray() {
return [...this];
}
}
// 使用示例
const collection = new Collection([1, 2, 3, 4, 5]);
// 使用 for...of 循环
for (const item of collection) {
console.log(item);
}
// 使用链式方法
const result = collection
.filter(item => item % 2 === 0) // 过滤偶数
.map(item => item * 2) // 每个元素乘以 2
.toArray(); // 转换为数组
console.log(result); // 输出: [4, 8]
异步迭代器
ES2018 引入了异步迭代器和异步迭代协议,允许异步地迭代数据源。
异步迭代协议
异步迭代协议由两部分组成:
- 异步可迭代协议:对象必须实现
Symbol.asyncIterator
方法 - 异步迭代器协议:返回的迭代器必须有一个返回 Promise 的
next()
方法
基本示例
// 创建一个异步可迭代对象
const asyncIterable = {
async *[Symbol.asyncIterator]() {
for (let i = 0; i < 5; i++) {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
};
// 使用 for await...of 循环
async function example() {
for await (const num of asyncIterable) {
console.log(num); // 每隔 1 秒输出: 0, 1, 2, 3, 4
}
}
example();
实际应用:流式处理
// 模拟一个异步数据流
class AsyncDataStream {
constructor(dataSource) {
this.dataSource = dataSource;
this.isClosed = false;
}
async *[Symbol.asyncIterator]() {
try {
while (!this.isClosed) {
const data = await this.dataSource.read();
if (data === null) {
break; // 数据流结束
}
yield data;
}
} finally {
if (!this.isClosed) {
this.close();
}
}
}
close() {
if (!this.isClosed) {
this.isClosed = true;
this.dataSource.close();
console.log('数据流已关闭');
}
}
}
// 模拟数据源
const mockDataSource = {
data: ['数据块1', '数据块2', '数据块3', '数据块4', '数据块5'],
position: 0,
async read() {
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 500));
if (this.position >= this.data.length) {
return null; // 没有更多数据
}
return this.data[this.position++];
},
close() {
console.log('数据源已关闭');
}
};
// 使用异步迭代器处理数据流
async function processStream() {
const stream = new AsyncDataStream(mockDataSource);
try {
for await (const chunk of stream) {
console.log('处理数据块:', chunk);
// 模拟处理过程中的错误
if (Math.random() < 0.3) {
throw new Error('处理数据时出错');
}
}
} catch (error) {
console.error('错误:', error.message);
} finally {
stream.close(); // 确保流被关闭
}
}
processStream();
迭代协议的最佳实践
1. 使迭代器自身可迭代
通过让迭代器实现 Symbol.iterator
方法并返回自身,可以使迭代器更加灵活:
function createBetterIterator(array) {
let index = 0;
return {
// 迭代器协议
next() {
if (index < array.length) {
return { value: array[index++], done: false };
} else {
return { done: true };
}
},
// 可迭代协议
[Symbol.iterator]() {
return this;
}
};
}
const iterator = createBetterIterator([1, 2, 3]);
// 作为迭代器使用
console.log(iterator.next().value); // 1
// 作为可迭代对象使用
for (const item of iterator) {
console.log(item); // 2, 3 (因为已经消费了 1)
}
2. 实现 return() 方法进行资源清理
function createResourceIterator(resource) {
let index = 0;
let closed = false;
// 模拟资源获取
console.log('获取资源:', resource);
return {
next() {
if (closed) {
return { done: true };
}
if (index < 3) {
return { value: `${resource}-${index++}`, done: false };
} else {
this.return();
return { done: true };
}
},
return() {
if (!closed) {
closed = true;
console.log('释放资源:', resource);
}
return { done: true };
},
[Symbol.iterator]() {
return this;
}
};
}
// 使用示例
const iterator = createResourceIterator('DB连接');
// 正常迭代
for (const item of iterator) {
console.log(item);
if (item === 'DB连接-1') {
break; // 提前退出循环,会调用 return()
}
}
3. 使用生成器简化迭代器创建
生成器函数提供了一种更简洁的方式来创建迭代器:
// 使用生成器创建范围迭代器
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
// 使用
for (const num of range(1, 10, 2)) {
console.log(num); // 输出: 1, 3, 5, 7, 9
}
4. 避免无限循环
使用无限迭代器时,确保有适当的终止条件:
// 无限迭代器
function* infiniteSequence() {
let i = 0;
while (true) {
yield i++;
}
}
// 安全使用
const iterator = infiniteSequence();
let count = 0;
for (const num of iterator) {
console.log(num);
if (++count >= 5) break; // 确保终止
}
总结
迭代协议是 JavaScript 中一个强大的特性,它允许对象定义或自定义它们的迭代行为。通过实现可迭代协议和迭代器协议,我们可以创建自定义的可迭代对象,使其能够在 for...of
循环、展开运算符和解构赋值等语法中使用。
迭代协议的主要优点包括:
- 统一的接口:为不同的数据结构提供统一的遍历方式
- 惰性计算:只在需要时才计算下一个值,提高效率
- 无限序列:可以表示无限序列而不消耗无限内存
- 自定义迭代行为:可以为任何对象定义自定义的迭代行为
- 与语言特性集成:与
for...of
、展开运算符等语言特性无缝集成
通过掌握迭代协议,我们可以更有效地处理各种数据结构,并创建更灵活、更强大的 JavaScript 应用程序。
在下一章中,我们将深入探讨生成器,它是创建迭代器的一种更简洁、更强大的方式。