迭代协议:for...of 背后的隐藏接口
"JavaScript 中的 for...of 循环看起来简单直接,但其背后隐藏着一套精巧的迭代协议。理解这套协议不仅能帮助我们更好地使用迭代功能,还能让我们创建自定义的可迭代对象。"
迭代是编程中的基本操作之一,在 JavaScript 中,ES6 引入了 for...of 循环和迭代协议,为遍历各种数据结构提供了统一的机制。这套协议不仅适用于内置的数组、字符串等类型,还允许我们为自定义对象定义迭代行为。
一、迭代协议基础概念
JavaScript 中有两种相关的协议:
- 可迭代协议(Iterable Protocol) - 允许对象定义自己的迭代行为
- 迭代器协议(Iterator Protocol) - 定义了产生一系列值的标准方式
可迭代协议
要成为可迭代对象,对象必须实现 @@iterator 方法,这意味着对象(或其原型链上的对象)必须有一个键为 Symbol.iterator 的属性,其值是一个无参数的函数,该函数返回一个符合迭代器协议的对象。
javascript
const iterable = {
[Symbol.iterator]() {
// 返回一个迭代器对象
return iterator;
}
};迭代器协议
迭代器对象必须实现 next() 方法,该方法返回一个具有以下两个属性的对象:
value- 序列中的下一个值done- 布尔值,表示迭代是否完成
javascript
const iterator = {
next() {
return {
value: 'some value',
done: false // 或 true 表示完成
};
}
};二、内置可迭代对象
JavaScript 的许多内置对象都是可迭代的:
javascript
// 数组
const arr = [1, 2, 3];
for (const value of arr) {
console.log(value); // 1, 2, 3
}
// 字符串
const str = "abc";
for (const char of str) {
console.log(char); // "a", "b", "c"
}
// Map
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(key, value); // "a" 1, "b" 2
}
// Set
const set = new Set([1, 2, 3]);
for (const value of set) {
console.log(value); // 1, 2, 3
}
// arguments 对象
function example() {
for (const arg of arguments) {
console.log(arg);
}
}
example(1, 2, 3); // 1, 2, 3
// NodeList
// for (const node of document.querySelectorAll('div')) {
// console.log(node);
// }三、手动实现迭代协议
让我们手动实现一个简单的可迭代对象:
javascript
const iterableObj = {
data: [1, 2, 3, 4, 5],
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next() {
if (index < data.length) {
return {
value: data[index++],
done: false
};
} else {
return {
done: true
};
}
}
};
}
};
// 使用 for...of 遍历
for (const value of iterableObj) {
console.log(value); // 1, 2, 3, 4, 5
}
// 使用扩展运算符
console.log([...iterableObj]); // [1, 2, 3, 4, 5]
// 使用 Array.from
console.log(Array.from(iterableObj)); // [1, 2, 3, 4, 5]四、使用生成器简化迭代器实现
生成器函数可以大大简化迭代器的实现:
javascript
const iterableObjWithGenerator = {
data: [1, 2, 3, 4, 5],
*[Symbol.iterator]() {
for (const item of this.data) {
yield item;
}
}
};
for (const value of iterableObjWithGenerator) {
console.log(value); // 1, 2, 3, 4, 5
}五、创建自定义可迭代对象
1. 范围迭代器
javascript
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const step = this.step;
const end = this.end;
return {
next() {
if (current < end) {
const value = current;
current += step;
return { value, done: false };
} else {
return { done: true };
}
}
};
}
}
const range = new Range(1, 10, 2);
for (const num of range) {
console.log(num); // 1, 3, 5, 7, 9
}
console.log([...range]); // [1, 3, 5, 7, 9]2. 无限序列迭代器
javascript
class FibonacciSequence {
[Symbol.iterator]() {
let prev = 0;
let curr = 1;
return {
next() {
const value = prev;
[prev, curr] = [curr, prev + curr];
return { value, done: false };
}
};
}
}
const fibonacci = new FibonacciSequence();
const fibIterator = fibonacci[Symbol.iterator]();
// 获取前10个斐波那契数
for (let i = 0; i < 10; i++) {
console.log(fibIterator.next().value);
}
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 343. 可中断的迭代器
javascript
class Countdown {
constructor(from) {
this.from = from;
}
[Symbol.iterator]() {
let current = this.from;
let cancelled = false;
const iterator = {
next() {
if (cancelled || current < 0) {
return { done: true };
}
return {
value: current--,
done: false
};
},
cancel() {
cancelled = true;
}
};
return iterator;
}
}
const countdown = new Countdown(5);
const iterator = countdown[Symbol.iterator]();
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: 4, done: false }
iterator.cancel(); // 取消迭代
console.log(iterator.next()); // { done: true }六、迭代协议的实际应用
1. 数据结构的自定义遍历
javascript
class TreeNode {
constructor(value) {
this.value = value;
this.children = [];
}
addChild(child) {
this.children.push(child);
}
*[Symbol.iterator]() {
// 深度优先遍历
yield this.value;
for (const child of this.children) {
yield* child;
}
}
*breadthFirst() {
// 广度优先遍历
const queue = [this];
while (queue.length > 0) {
const node = queue.shift();
yield node.value;
queue.push(...node.children);
}
}
}
// 创建树结构
const root = new TreeNode('root');
const child1 = new TreeNode('child1');
const child2 = new TreeNode('child2');
const grandchild1 = new TreeNode('grandchild1');
root.addChild(child1);
root.addChild(child2);
child1.addChild(grandchild1);
// 深度优先遍历
console.log([...root]); // ["root", "child1", "grandchild1", "child2"]
// 广度优先遍历
console.log([...root.breadthFirst()]); // ["root", "child1", "child2", "grandchild1"]2. 数据流处理
javascript
class DataStream {
constructor(data) {
this.data = data;
}
*[Symbol.iterator]() {
for (const item of this.data) {
// 模拟数据处理
yield item.toUpperCase();
}
}
*filter(predicate) {
for (const item of this) {
if (predicate(item)) {
yield item;
}
}
}
*map(transform) {
for (const item of this) {
yield transform(item);
}
}
}
const stream = new DataStream(['hello', 'world', 'javascript']);
const processed = stream
.map(str => str.toUpperCase())
.filter(str => str.length > 5);
console.log([...processed]); // ["JAVASCRIPT"]七、与其他遍历方法的对比
javascript
const data = [1, 2, 3, 4, 5];
// for...of 循环
for (const item of data) {
console.log(item);
}
// forEach 方法
data.forEach(item => console.log(item));
// for 循环
for (let i = 0; i < data.length; i++) {
console.log(data[i]);
}
// for...in 循环(不推荐用于数组)
for (const key in data) {
console.log(data[key]);
}for...of 的优势:
- 语法简洁
- 只遍历可枚举属性
- 适用于所有可迭代对象
- 可以使用 break、continue 和 return
八、错误处理和边界情况
javascript
const brokenIterable = {
[Symbol.iterator]() {
let count = 0;
return {
next() {
if (count++ < 3) {
return { value: count, done: false };
} else {
throw new Error('迭代器出错');
}
}
};
}
};
try {
for (const value of brokenIterable) {
console.log(value);
}
} catch (e) {
console.log('捕获到错误:', e.message); // 捕获到错误: 迭代器出错
}一句话总结
JavaScript 的迭代协议通过 Symbol.iterator 和 next() 方法为各种数据结构提供了统一的遍历机制,不仅适用于内置对象,还可以通过自定义实现让任何对象支持 for...of 循环和扩展运算符等语法。
理解迭代协议有助于我们更好地使用 JavaScript 的现代特性,创建更灵活的数据结构,并实现复杂的迭代逻辑。通过生成器函数,我们可以更简洁地实现迭代器,使代码更加清晰易读。