for...of 与可迭代协议:Symbol.iterator 如何统一遍历逻辑?
理解可迭代协议
ES6 引入了可迭代协议(Iterable Protocol),它定义了 JavaScript 对象如何被迭代。这个协议基于 Symbol.iterator 这个内置 Symbol,使得不同的数据结构可以使用统一的遍历方式。
可迭代协议的基本概念
javascript
// 可迭代对象必须实现 @@iterator 方法
// @@iterator 是 Symbol.iterator 的别名
const iterable = {
data: [1, 2, 3, 4, 5],
// 实现 @@iterator 方法
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
// 迭代器必须实现 next 方法
next() {
if (index < data.length) {
return {
value: data[index++],
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
};
}
};
// 现在可以使用 for...of 遍历
for (const value of iterable) {
console.log(value); // 1, 2, 3, 4, 5
}
// 也可以使用其他接受可迭代对象的方法
console.log([...iterable]); // [1, 2, 3, 4, 5]
console.log(Array.from(iterable)); // [1, 2, 3, 4, 5]内置的可迭代对象
数组的可迭代性
javascript
// 数组原生支持可迭代协议
const arr = [1, 2, 3];
// 使用 for...of 遍历
for (const value of arr) {
console.log(value); // 1, 2, 3
}
// 获取迭代器
const iterator = arr[Symbol.iterator]();
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()); // { value: undefined, done: true }字符串的可迭代性
javascript
// 字符串也支持可迭代协议
const str = 'Hello';
// 遍历字符串中的每个字符
for (const char of str) {
console.log(char); // H, e, l, l, o
}
// 处理 Unicode 字符
const emojiStr = '😀😃😄😁';
for (const emoji of emojiStr) {
console.log(emoji); // 😀, 😃, 😄, 😁
}
// 与 String.prototype.charAt() 的区别
console.log([...str]); // ['H', 'e', 'l', 'l', 'o']
console.log(Array.from(str)); // ['H', 'e', 'l', 'l', 'o']Map 和 Set 的可迭代性
javascript
// Map 的可迭代性
const map = new Map([
['name', 'Alice'],
['age', 30],
['city', 'New York']
]);
// 默认迭代 Map 返回 [key, value] 对
for (const entry of map) {
console.log(entry); // ['name', 'Alice'], ['age', 30], ['city', 'New York']
}
// 显式使用 entries()
for (const entry of map.entries()) {
console.log(entry); // ['name', 'Alice'], ['age', 30], ['city', 'New York']
}
// 只迭代键
for (const key of map.keys()) {
console.log(key); // name, age, city
}
// 只迭代值
for (const value of map.values()) {
console.log(value); // Alice, 30, New York
}
// Set 的可迭代性
const set = new Set([1, 2, 3, 2, 1]); // 自动去重
for (const value of set) {
console.log(value); // 1, 2, 3
}自定义可迭代对象
基本自定义迭代器
javascript
// 创建一个简单的计数器可迭代对象
class Counter {
constructor(max) {
this.max = max;
}
[Symbol.iterator]() {
let count = 0;
const max = this.max;
return {
next() {
if (count < max) {
return {
value: count++,
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
};
}
}
const counter = new Counter(3);
for (const value of counter) {
console.log(value); // 0, 1, 2
}
console.log([...counter]); // [0, 1, 2]生成器实现的迭代器
javascript
// 使用生成器简化迭代器实现
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
*[Symbol.iterator]() {
for (let i = this.start; i < this.end; i += this.step) {
yield i;
}
}
}
const range = new Range(1, 10, 2);
for (const value of range) {
console.log(value); // 1, 3, 5, 7, 9
}
console.log([...range]); // [1, 3, 5, 7, 9]无限迭代器
javascript
// 创建无限序列迭代器
class Fibonacci {
*[Symbol.iterator]() {
let prev = 0, curr = 1;
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
}
const fibonacci = new Fibonacci();
// 使用 break 控制无限迭代
let count = 0;
for (const value of fibonacci) {
console.log(value);
if (++count >= 10) break;
}
// 输出前10个斐波那契数: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55迭代器协议详解
迭代器的 next 方法
javascript
// 迭代器的 next 方法返回 { value, done } 对象
class SimpleIterator {
constructor(data) {
this.data = data;
this.index = 0;
}
next() {
if (this.index < this.data.length) {
return {
value: this.data[this.index++],
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
}
const iterator = new SimpleIterator(['a', 'b', 'c']);
console.log(iterator.next()); // { value: 'a', done: false }
console.log(iterator.next()); // { value: 'b', done: false }
console.log(iterator.next()); // { value: 'c', done: false }
console.log(iterator.next()); // { value: undefined, done: true }迭代器的 return 和 throw 方法
javascript
// 完整的迭代器协议还包括 return 和 throw 方法
class AdvancedIterator {
constructor(data) {
this.data = data;
this.index = 0;
}
next() {
console.log('next called');
if (this.index < this.data.length) {
return {
value: this.data[this.index++],
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
// 当迭代提前结束时调用(如 break 语句)
return(value) {
console.log('return called');
return {
value,
done: true
};
}
// 当迭代中抛出异常时调用
throw(error) {
console.log('throw called with:', error.message);
return {
value: undefined,
done: true
};
}
}
const advancedIterator = new AdvancedIterator([1, 2, 3]);
// 在 for...of 中使用 break 会触发 return 方法
for (const value of advancedIterator) {
console.log(value);
if (value === 2) break; // 触发 return 方法
}
// 输出:
// next called
// 1
// next called
// 2
// return called生成器与迭代器
生成器函数基础
javascript
// 生成器函数使用 function* 语法
function* simpleGenerator() {
yield 1;
yield 2;
yield 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: false }
console.log(gen.next()); // { value: undefined, done: true }生成器作为迭代器
javascript
// 生成器自动实现迭代器协议
class Collection {
constructor(items) {
this.items = items;
}
*[Symbol.iterator]() {
for (const item of this.items) {
yield item;
}
}
}
const collection = new Collection([1, 2, 3, 4, 5]);
for (const item of collection) {
console.log(item); // 1, 2, 3, 4, 5
}
// 生成器也可以接收参数
function* counter(start = 0) {
let count = start;
while (true) {
const increment = yield count;
if (increment !== undefined) {
count += increment;
} else {
count++;
}
}
}
const countGen = counter(10);
console.log(countGen.next().value); // 10
console.log(countGen.next().value); // 11
console.log(countGen.next(5).value); // 16 (11 + 5)
console.log(countGen.next().value); // 17生成器委托
javascript
// 使用 yield* 委托给其他生成器
function* generator1() {
yield 1;
yield 2;
}
function* generator2() {
yield 3;
yield 4;
}
function* combinedGenerator() {
yield* generator1();
yield* generator2();
yield 5;
}
console.log([...combinedGenerator()]); // [1, 2, 3, 4, 5]
// 委托给其他可迭代对象
function* arrayGenerator() {
yield* [6, 7, 8];
yield* 'abc';
}
console.log([...arrayGenerator()]); // [6, 7, 8, 'a', 'b', 'c']实际应用场景
1. 自定义数据结构
javascript
// 为二叉树实现可迭代协议
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class BinaryTree {
constructor() {
this.root = null;
}
// 中序遍历的迭代器
*[Symbol.iterator]() {
yield* this.inorderTraversal(this.root);
}
*inorderTraversal(node) {
if (node) {
yield* this.inorderTraversal(node.left);
yield node.value;
yield* this.inorderTraversal(node.right);
}
}
insert(value) {
// 简化的插入实现
if (!this.root) {
this.root = new TreeNode(value);
} else {
this.insertNode(this.root, value);
}
}
insertNode(node, value) {
if (value < node.value) {
if (!node.left) {
node.left = new TreeNode(value);
} else {
this.insertNode(node.left, value);
}
} else {
if (!node.right) {
node.right = new TreeNode(value);
} else {
this.insertNode(node.right, value);
}
}
}
}
const tree = new BinaryTree();
tree.insert(5);
tree.insert(3);
tree.insert(7);
tree.insert(2);
tree.insert(4);
for (const value of tree) {
console.log(value); // 2, 3, 4, 5, 7 (中序遍历)
}2. 异步迭代器
javascript
// 异步迭代器用于处理异步数据流
class AsyncDataSource {
constructor(data) {
this.data = data;
}
async *[Symbol.asyncIterator]() {
for (const item of this.data) {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 100));
yield item;
}
}
}
async function processAsyncData() {
const dataSource = new AsyncDataSource([1, 2, 3, 4, 5]);
// 使用 for await...of 遍历异步迭代器
for await (const value of dataSource) {
console.log(value);
}
}
// processAsyncData();
// 每100ms输出一个数字: 1, 2, 3, 4, 53. 对象属性遍历
javascript
// 为对象实现自定义迭代行为
Object.prototype[Symbol.iterator] = function* () {
for (const key in this) {
if (this.hasOwnProperty(key)) {
yield [key, this[key]];
}
}
};
const obj = {
name: 'Alice',
age: 30,
city: 'New York'
};
for (const [key, value] of obj) {
console.log(`${key}: ${value}`);
}
// name: Alice
// age: 30
// city: New York与其他遍历方式的对比
for...of vs for...in
javascript
const arr = ['a', 'b', 'c'];
arr.customProperty = 'custom';
// for...in 遍历所有可枚举属性(包括原型链上的)
for (const key in arr) {
console.log(key); // 0, 1, 2, customProperty
}
// for...of 只遍历可迭代对象的值
for (const value of arr) {
console.log(value); // a, b, c
}for...of vs forEach
javascript
const arr = [1, 2, 3];
// for...of 可以使用 break 和 continue
for (const value of arr) {
if (value === 2) continue;
if (value === 3) break;
console.log(value); // 1
}
// forEach 不能使用 break 和 continue
arr.forEach(value => {
// 不能中断 forEach 循环
console.log(value); // 1, 2, 3
});最佳实践
1. 合理实现可迭代协议
javascript
// 好的做法:为自定义集合类实现可迭代协议
class CustomSet {
constructor(items = []) {
this.items = [];
for (const item of items) {
this.add(item);
}
}
add(item) {
if (!this.has(item)) {
this.items.push(item);
}
}
has(item) {
return this.items.includes(item);
}
// 实现可迭代协议
[Symbol.iterator]() {
return this.items[Symbol.iterator]();
}
}
const customSet = new CustomSet([1, 2, 3]);
for (const item of customSet) {
console.log(item); // 1, 2, 3
}2. 使用生成器简化实现
javascript
// 使用生成器简化复杂迭代器的实现
class Matrix {
constructor(rows, cols, initialValue = 0) {
this.rows = rows;
this.cols = cols;
this.data = Array(rows).fill().map(() => Array(cols).fill(initialValue));
}
set(row, col, value) {
if (row >= 0 && row < this.rows && col >= 0 && col < this.cols) {
this.data[row][col] = value;
}
}
get(row, col) {
if (row >= 0 && row < this.rows && col >= 0 && col < this.cols) {
return this.data[row][col];
}
}
// 生成器实现矩阵元素的迭代
*[Symbol.iterator]() {
for (let row = 0; row < this.rows; row++) {
for (let col = 0; col < this.cols; col++) {
yield this.data[row][col];
}
}
}
// 生成器实现矩阵坐标的迭代
*coordinates() {
for (let row = 0; row < this.rows; row++) {
for (let col = 0; col < this.cols; col++) {
yield [row, col];
}
}
}
}
const matrix = new Matrix(2, 3);
matrix.set(0, 0, 1);
matrix.set(0, 1, 2);
matrix.set(1, 2, 3);
console.log([...matrix]); // [1, 2, 0, 0, 0, 3]
for (const [row, col] of matrix.coordinates()) {
console.log(`(${row}, ${col}): ${matrix.get(row, col)}`);
}3. 注意性能考虑
javascript
// 对于简单的数组遍历,原生 for 循环可能更快
const arr = new Array(1000000).fill(1);
// 性能测试示例
console.time('for loop');
let sum1 = 0;
for (let i = 0; i < arr.length; i++) {
sum1 += arr[i];
}
console.timeEnd('for loop');
console.time('for...of');
let sum2 = 0;
for (const value of arr) {
sum2 += value;
}
console.timeEnd('for...of');总结
for...of 循环和可迭代协议通过 Symbol.iterator 提供了一种统一的遍历机制,使得不同的数据结构可以使用相同的语法进行遍历。理解可迭代协议和迭代器协议的工作原理,有助于我们创建更加灵活和一致的数据结构。通过生成器函数,我们可以更简洁地实现迭代器,而异步迭代器则为处理异步数据流提供了强大的工具。在实际开发中,合理使用这些特性可以让我们编写出更加优雅和高效的代码。