一、迭代器(Iterator)
1. 迭代器协议
迭代器协议定义了如何顺序访问集合中的元素 的标准方式。一个对象要成为迭代器,必须实现 next()
方法,该方法返回包含两个属性的对象:
value
:当前迭代的值done
:布尔值,表示迭代是否完成
javascript
const simpleIterator = {
data: [1, 2, 3],
index: 0,
next() {
return this.index < this.data.length
? { value: this.data[this.index++], done: false }
: { value: undefined, done: true };
}
};
console.log(simpleIterator.next()); // { value: 1, done: false }
console.log(simpleIterator.next()); // { value: 2, done: false }
console.log(simpleIterator.next()); // { value: 3, done: false }
console.log(simpleIterator.next()); // { value: undefined, done: true }
2. 可迭代协议
可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为 。要成为可迭代对象,必须实现 @@iterator
方法(通过 Symbol.iterator
访问):
javascript
const iterableObject = {
items: ['a', 'b', 'c'],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
return index < this.items.length
? { value: this.items[index++], done: false }
: { value: undefined, done: true };
}
};
}
};
for (const item of iterableObject) {
console.log(item); // 'a', 'b', 'c'
}
3. 内置可迭代对象
JavaScript 中许多内置对象都是可迭代的:
Array
String
Map
Set
TypedArray
arguments
对象- DOM 集合(如 NodeList)
javascript
// 字符串迭代
for (const char of 'hello') {
console.log(char); // h, e, l, l, o
}
// Map 迭代
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(key, value); // a 1, b 2
}
4. 迭代器的使用场景
-
解构赋值:
javascriptconst [first, second] = [1, 2, 3]; console.log(first, second); // 1 2
-
扩展运算符:
javascriptconst arr = [...'hello']; console.log(arr); // ['h', 'e', 'l', 'l', 'o']
-
for...of 循环:
javascriptconst set = new Set([1, 2, 3]); for (const num of set) { console.log(num); // 1, 2, 3 }
-
接受可迭代对象的 API:
javascriptnew Map([[1, 'a'], [2, 'b']]); Promise.all(iterable); Array.from(iterable);
二、生成器(Generator)
1. 生成器函数
生成器函数是能暂停和恢复执行 的特殊函数,使用 function*
语法定义:
javascript
function* simpleGenerator() {
yield 1;
yield 2;
return 3;
}
const gen = simpleGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }
2. 生成器特性
-
yield 表达式:
- 暂停函数执行并返回一个值
- 可以通过
next()
恢复执行
-
双向通信:
next(value)
可以向生成器内部传递值yield
可以接收外部传入的值
javascript
function* twoWayGenerator() {
const name = yield 'What is your name?';
yield `Hello, ${name}!`;
}
const gen = twoWayGenerator();
console.log(gen.next().value); // "What is your name?"
console.log(gen.next('Alice').value); // "Hello, Alice!"
- 提前终止 :
generator.return()
:终止生成器generator.throw()
:向生成器抛出错误
javascript
function* gen() {
try {
yield 1;
yield 2;
} catch (e) {
console.log('Error caught:', e);
}
}
const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.throw(new Error('Something went wrong')));
// "Error caught: Error: Something went wrong"
// { value: undefined, done: true }
3. 生成器作为迭代器
生成器对象也是迭代器,可以用于实现可迭代对象:
javascript
const iterableObject = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
};
for (const num of iterableObject) {
console.log(num); // 1, 2, 3
}
4. 异步生成器
ES2018 引入了异步生成器,用于处理异步迭代:
javascript
async function* asyncGenerator() {
const data1 = await fetchData1();
yield data1;
const data2 = await fetchData2();
yield data2;
}
// 使用 for await...of
(async () => {
for await (const data of asyncGenerator()) {
console.log(data);
}
})();
三、迭代器与生成器的实际应用
1. 实现自定义数据结构迭代
javascript
class TreeNode {
constructor(value, left = null, right = null) {
this.value = value;
this.left = left;
this.right = right;
}
*[Symbol.iterator]() {
yield this.value;
if (this.left) yield* this.left;
if (this.right) yield* this.right;
}
}
const tree = new TreeNode(1,
new TreeNode(2, new TreeNode(3)),
new TreeNode(4)
);
for (const value of tree) {
console.log(value); // 1, 2, 3, 4
}
2. 无限序列
javascript
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
// 可以无限继续
3. 分页数据获取
javascript
async function* paginatedData(url) {
let page = 1;
while (true) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
if (data.length === 0) return;
yield* data;
page++;
}
}
// 使用
(async () => {
for await (const item of paginatedData('/api/data')) {
console.log(item);
}
})();
4. 状态机实现
javascript
function* stateMachine() {
let state = 'start';
while (true) {
const input = yield state;
switch (state) {
case 'start':
state = input === 'next' ? 'middle' : 'error';
break;
case 'middle':
state = input === 'next' ? 'end' : 'error';
break;
case 'end':
return 'done';
case 'error':
throw new Error('Invalid state transition');
}
}
}
const sm = stateMachine();
console.log(sm.next().value); // 'start'
console.log(sm.next('next').value); // 'middle'
console.log(sm.next('next').value); // 'end'
console.log(sm.next().done); // true
四、迭代器与生成器的区别与联系
特性 | 迭代器 | 生成器 |
---|---|---|
创建方式 | 手动实现 next() 方法 | 使用 function* 定义 |
状态管理 | 需要手动维护状态 | 自动维护暂停/恢复状态 |
语法复杂度 | 相对复杂 | 更简洁 |
双向通信 | 只能单向输出值 | 可通过 yield 和 next() 双向通信 |
实现可迭代对象 | 需要实现 Symbol.iterator | 本身就是可迭代对象 |
异步支持 | 需要额外实现 | 原生支持异步生成器 |
五、最佳实践与注意事项
1. 迭代器最佳实践
-
实现完整的迭代器协议:
- 确保
next()
方法返回{ value, done }
对象 - 迭代完成后继续调用应返回
{ value: undefined, done: true }
- 确保
-
考虑可重用性:
- 每次调用
[Symbol.iterator]()
应返回新的迭代器 - 避免共享迭代器状态
- 每次调用
-
处理清理逻辑:
- 如果迭代涉及资源(如文件句柄),实现
return()
方法进行清理
- 如果迭代涉及资源(如文件句柄),实现
2. 生成器最佳实践
-
合理使用 yield:
- 避免在循环中无意义的 yield
- 考虑 yield 的性能开销
-
错误处理:
- 使用 try-catch 包裹可能出错的 yield
- 考虑实现 throw() 方法的处理逻辑
-
资源管理:
-
对于需要清理的资源,使用
try-finally
:javascriptfunction* resourceGenerator() { const resource = allocateResource(); try { yield resource; } finally { resource.release(); } }
-
3. 常见错误
-
忘记生成器是惰性的:
javascriptfunction* gen() { console.log('Start'); yield; console.log('End'); } // 错误:以为会立即执行 const g = gen(); // 不会有输出 g.next(); // 输出 "Start" g.next(); // 输出 "End"
-
误解 yield 的返回值:
javascriptfunction* gen() { const result = yield 'question'; // result 由 next() 的参数决定 console.log(result); } const g = gen(); g.next(); // { value: 'question', done: false } g.next('answer'); // 输出 "answer"
-
滥用无限生成器:
javascriptfunction* infinite() { while (true) yield Math.random(); } // 危险:可能导致内存问题 const arr = [...infinite()]; // 永远不会结束
六、现代 JavaScript 中的发展
1. 异步迭代协议 (ES2018)
异步迭代器协议与常规迭代器类似,但:
- 使用
Symbol.asyncIterator
而非Symbol.iterator
next()
返回 Promise 解析为{ value, done }
- 使用
for await...of
循环
javascript
const asyncIterable = {
[Symbol.asyncIterator]: async function* () {
yield 'hello';
await new Promise(resolve => setTimeout(resolve, 1000));
yield 'world';
}
};
(async () => {
for await (const item of asyncIterable) {
console.log(item); // 'hello', 1秒后 'world'
}
})();
2. 生成器委托 (yield*)
yield*
表达式用于委托给另一个生成器或可迭代对象:
javascript
function* gen1() {
yield 2;
yield 3;
}
function* gen2() {
yield 1;
yield* gen1(); // 委托给 gen1
yield 4;
}
console.log([...gen2()]); // [1, 2, 3, 4]
3. 与 async/await 结合
生成器可以简化异步代码:
javascript
function* asyncGenerator() {
const result1 = yield fetchData1();
const result2 = yield fetchData2(result1);
return result2;
}
// 运行函数
function runGenerator(generator) {
const gen = generator();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value)
.then(res => handle(gen.next(res)))
.catch(err => handle(gen.throw(err)));
}
return handle(gen.next());
}
// 使用
runGenerator(asyncGenerator)
.then(finalResult => console.log(finalResult));
七、总结
迭代器和生成器是 JavaScript 强大的编程工具:
-
迭代器:
- 提供了统一的集合访问接口
- 使自定义数据结构可迭代
- 是许多语言特性(如 for...of、扩展运算符)的基础
-
生成器:
- 允许函数暂停和恢复执行
- 简化迭代器创建
- 实现协程和高级控制流
- 为异步编程提供更直观的解决方案
掌握这些概念可以:
- 编写更清晰、更灵活的代码
- 实现复杂的数据处理流程
- 创建自定义的可迭代数据结构
- 更好地理解和利用现代 JavaScript 特性(如 async/await)