Skip to content

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, 5

3. 对象属性遍历

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 提供了一种统一的遍历机制,使得不同的数据结构可以使用相同的语法进行遍历。理解可迭代协议和迭代器协议的工作原理,有助于我们创建更加灵活和一致的数据结构。通过生成器函数,我们可以更简洁地实现迭代器,而异步迭代器则为处理异步数据流提供了强大的工具。在实际开发中,合理使用这些特性可以让我们编写出更加优雅和高效的代码。