
1. 理解JavaScript中的可迭代对象在JavaScript中可迭代对象(iterable)是指实现了Symbol.iterator方法的对象。这个方法返回一个迭代器(iterator)对象该迭代器必须实现next()方法。当我们在代码中使用for...of循环时实际上就是在调用这个迭代器接口。重要提示不是所有对象默认都是可迭代的。比如普通的{}对象直接使用for...of会抛出TypeError必须手动实现迭代器协议才能使用。1.1 迭代器协议详解迭代器协议包含两个核心部分可迭代对象必须实现Symbol.iterator方法该方法返回的迭代器必须包含next()方法next()方法需要返回一个包含两个属性的对象value当前迭代的值done布尔值表示迭代是否结束const myIterable { [Symbol.iterator]() { let step 0; return { next() { step; if (step 3) { return { value: Step ${step}, done: false }; } return { value: undefined, done: true }; } }; } };2. 实现自定义可迭代对象2.1 基本实现方式让我们创建一个简单的范围迭代器模拟Python中的range()函数class Range { constructor(start, end, step 1) { this.start start; this.end end; this.step step; } [Symbol.iterator]() { let current this.start; const end this.end; const step this.step; return { next() { if ((step 0 current end) || (step 0 current end)) { const value current; current step; return { value, done: false }; } return { done: true }; } }; } } // 使用示例 for (const num of new Range(1, 5)) { console.log(num); // 输出1, 2, 3, 4 }2.2 更复杂的例子树形结构迭代对于复杂数据结构我们可以实现不同的迭代方式class TreeNode { constructor(value) { this.value value; this.children []; } addChild(child) { this.children.push(child); return this; } *[Symbol.iterator]() { yield this.value; for (const child of this.children) { yield* child; } } } // 使用示例 const tree new TreeNode(root) .addChild(new TreeNode(child1) .addChild(new TreeNode(grandchild1)) .addChild(new TreeNode(grandchild2))) .addChild(new TreeNode(child2)); for (const value of tree) { console.log(value); // 输出: root, child1, grandchild1, grandchild2, child2 }3. 生成器函数简化迭代器实现ES6引入的生成器函数(generator function)可以大大简化迭代器的实现const fibonacci { *[Symbol.iterator]() { let [prev, curr] [0, 1]; while (true) { yield curr; [prev, curr] [curr, prev curr]; } } }; // 使用示例 let count 0; for (const num of fibonacci) { console.log(num); if (count 10) break; }注意事项无限迭代器一定要有终止条件否则会导致无限循环4. 内置可迭代对象分析JavaScript中许多内置对象已经是可迭代的4.1 数组和字符串// 数组 for (const item of [1, 2, 3]) { console.log(item); } // 字符串 for (const char of hello) { console.log(char); }4.2 Map和Setconst map new Map([ [a, 1], [b, 2] ]); for (const [key, value] of map) { console.log(key, value); } const set new Set([1, 2, 3]); for (const value of set) { console.log(value); }4.3 arguments和NodeListfunction iterateArgs() { for (const arg of arguments) { console.log(arg); } } iterateArgs(1, 2, 3); // NodeList for (const node of document.querySelectorAll(div)) { console.log(node); }5. 高级迭代技巧5.1 迭代器组合我们可以组合多个迭代器创建更复杂的功能function* zip(...iterables) { const iterators iterables.map(i i[Symbol.iterator]()); while (true) { const results iterators.map(iter iter.next()); if (results.some(r r.done)) break; yield results.map(r r.value); } } // 使用示例 const zipped zip([1, 2, 3], [a, b, c]); for (const [num, char] of zipped) { console.log(num, char); }5.2 异步迭代器ES2018引入了异步迭代器用于处理异步数据流const asyncIterable { [Symbol.asyncIterator]() { let i 0; return { next() { if (i 3) { return Promise.resolve({ value: i, done: false }); } return Promise.resolve({ done: true }); } }; } }; (async function() { for await (const item of asyncIterable) { console.log(item); } })();6. 常见问题与解决方案6.1 普通对象如何变成可迭代const obj { a: 1, b: 2, c: 3 }; // 方法1使用Object.entries obj[Symbol.iterator] function*() { for (const [key, value] of Object.entries(this)) { yield { key, value }; } }; // 方法2直接实现 obj[Symbol.iterator] function() { const keys Object.keys(this); let index 0; return { next: () { if (index keys.length) { const key keys[index]; return { value: { key, value: this[key] }, done: false }; } return { done: true }; } }; }; for (const {key, value} of obj) { console.log(key, value); }6.2 迭代过程中的修改处理class SafeArray extends Array { *[Symbol.iterator]() { const copy [...this]; for (const item of copy) { yield item; } } } const arr new SafeArray(1, 2, 3); for (const item of arr) { console.log(item); if (item 2) { arr.push(4); // 不会影响当前迭代 } }6.3 性能优化技巧对于大型数据集考虑使用生成器实现惰性求值避免在迭代过程中修改被迭代的集合对于数组等线性结构直接使用索引可能比迭代器更快// 性能对比 const bigArray Array(1e6).fill(0); console.time(for-of); for (const item of bigArray) {} console.timeEnd(for-of); console.time(for); for (let i 0; i bigArray.length; i) {} console.timeEnd(for);7. 实际应用场景7.1 数据分页处理class Paginator { constructor(fetchPage, pageSize 10) { this.fetchPage fetchPage; this.pageSize pageSize; } async *[Symbol.asyncIterator]() { let page 0; while (true) { const items await this.fetchPage(page, this.pageSize); if (!items.length) break; for (const item of items) { yield item; } } } } // 使用示例 const mockAPI (page, size) new Promise(resolve setTimeout(() resolve(page 3 ? Array(size).fill(0).map((_, i) Page ${page} Item ${i}) : []), 500 ) ); (async () { const paginator new Paginator(mockAPI); for await (const item of paginator) { console.log(item); } })();7.2 自定义数据结构遍历class Graph { constructor() { this.nodes new Map(); } addNode(name) { this.nodes.set(name, new Set()); } addEdge(from, to) { this.nodes.get(from).add(to); } *bfs(start) { const visited new Set(); const queue [start]; while (queue.length) { const node queue.shift(); if (visited.has(node)) continue; visited.add(node); yield node; for (const neighbor of this.nodes.get(node)) { if (!visited.has(neighbor)) { queue.push(neighbor); } } } } } // 使用示例 const graph new Graph(); [A, B, C, D].forEach(node graph.addNode(node)); graph.addEdge(A, B); graph.addEdge(A, C); graph.addEdge(B, D); graph.addEdge(C, D); for (const node of graph.bfs(A)) { console.log(node); // 输出: A, B, C, D }7.3 与解构赋值结合使用const [first, second] new Set([1, 2, 3]); console.log(first, second); // 1, 2 const [head, ...tail] hello; console.log(head, tail); // h, [e, l, l, o]8. 迭代器协议与其他语言特性的结合8.1 扩展运算符const myIterable { *[Symbol.iterator]() { yield 1; yield 2; yield 3; } }; const arr [...myIterable]; // [1, 2, 3] console.log(arr);8.2 Array.fromconst arrayLike { length: 3, 0: a, 1: b, 2: c, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; const arr Array.from(arrayLike); // [a, b, c] console.log(arr);8.3 Promise.allconst promises [ Promise.resolve(1), Promise.resolve(2), Promise.resolve(3) ]; Promise.all(promises).then(values { for (const value of values) { console.log(value); } });9. 迭代器的高级控制9.1 手动控制迭代const iterable { [Symbol.iterator]() { let count 0; return { next() { return { value: count, done: false }; }, return() { console.log(迭代提前终止); return { done: true }; }, throw(error) { console.log(迭代抛出异常:, error); return { done: true }; } }; } }; const iterator iterable[Symbol.iterator](); console.log(iterator.next()); // { value: 0, done: false } console.log(iterator.next()); // { value: 1, done: false } iterator.return(); // 输出: 迭代提前终止9.2 可关闭的迭代器function* closableGenerator() { try { let count 0; while (true) { yield count; } } finally { console.log(执行清理工作); } } const gen closableGenerator(); console.log(gen.next()); // { value: 0, done: false } console.log(gen.next()); // { value: 1, done: false } gen.return(); // 输出: 执行清理工作10. 性能考量与最佳实践10.1 迭代器与内存使用生成器实现的迭代器特别适合处理大型数据集因为它们可以按需生成值而不需要一次性将所有数据加载到内存中。function* bigDataGenerator() { for (let i 0; i 1e6; i) { yield i; } } // 内存友好 for (const num of bigDataGenerator()) { if (num 10) break; console.log(num); }10.2 何时使用迭代器适合使用迭代器的场景处理大型或无限数据集需要自定义遍历逻辑需要惰性求值与其他语言特性(如解构、扩展运算符)配合使用不适合的场景简单的数组遍历(直接使用for循环可能更高效)需要随机访问元素性能关键的代码段10.3 迭代器与函数式编程迭代器可以与函数式编程方法很好地结合function* filter(iterable, predicate) { for (const item of iterable) { if (predicate(item)) { yield item; } } } function* map(iterable, mapper) { for (const item of iterable) { yield mapper(item); } } const numbers [1, 2, 3, 4, 5]; const result [ ...map( filter(numbers, n n % 2 0), n n * 2 ) ]; console.log(result); // [4, 8]11. 浏览器兼容性与polyfill虽然现代浏览器都支持迭代器协议但在旧环境中可能需要polyfill// 简单的Symbol.iterator polyfill if (!Symbol.iterator) { Symbol.iterator Symbol(iterator); } // 为Object添加默认迭代器 if (!Object.prototype[Symbol.iterator]) { Object.prototype[Symbol.iterator] function*() { for (const key in this) { if (this.hasOwnProperty(key)) { yield [key, this[key]]; } } }; }注意修改内置对象的原型可能带来意想不到的副作用在生产环境中要谨慎使用12. 测试迭代器实现为迭代器编写测试用例很重要function testIterator(iterableFactory) { const iterable iterableFactory(); const iterator iterable[Symbol.iterator](); // 测试1: 迭代器对象是否存在 console.assert(iterator ! null, 迭代器对象不存在); // 测试2: next方法是否存在 console.assert(typeof iterator.next function, next方法不存在); // 测试3: 迭代结果是否符合预期 const results []; for (const item of iterable) { results.push(item); } console.assert(results.length 0, 迭代未产生任何结果); return results; } // 使用示例 const rangeResults testIterator(() new Range(1, 5)); console.assert(rangeResults.length 4, Range迭代结果数量不正确); console.assert(rangeResults[0] 1, Range第一个值不正确);13. 调试迭代器代码调试迭代器时的一些技巧使用debugger语句暂停执行在迭代器方法中添加日志检查done和value属性const debugIterable { [Symbol.iterator]() { console.log(迭代器创建); let count 0; return { next() { console.log(next调用count${count}); if (count 3) { return { value: count, done: false }; } return { done: true }; }, return() { console.log(迭代器提前返回); return { done: true }; } }; } }; for (const item of debugIterable) { console.log(item); if (item 1) break; } // 输出: // 迭代器创建 // next调用count0 // 0 // next调用count1 // 1 // 迭代器提前返回14. 迭代器与生成器的区别虽然生成器常用于实现迭代器但它们有重要区别特性迭代器生成器创建方式手动实现对象使用function*状态管理需要自行维护自动暂停/恢复语法复杂度较高较低错误处理需要手动实现可以使用try/catch适用场景简单迭代需求复杂迭代逻辑// 迭代器实现 const iterator { data: [1, 2, 3], index: 0, next() { return this.index this.data.length ? { value: this.data[this.index], done: false } : { done: true }; } }; // 生成器实现 function* generator() { yield 1; yield 2; yield 3; }15. 迭代器模式的设计优势使用迭代器模式的主要好处统一的访问接口不同数据结构可以使用相同的遍历方式惰性求值只在需要时才计算下一个值并行迭代可以同时遍历多个集合内部实现封装迭代逻辑与数据结构分离无限序列可以表示无限的数据流// 无限序列示例 function* naturalNumbers() { let n 0; while (true) { yield n; } } // 并行迭代示例 function* zip(...iterables) { const iterators iterables.map(i i[Symbol.iterator]()); while (true) { const results iterators.map(iter iter.next()); if (results.some(r r.done)) break; yield results.map(r r.value); } }16. 与其他语言的迭代器对比JavaScript的迭代器与其他语言类似概念的比较语言概念主要区别Python迭代器协议使用__iter__和__next__方法JavaIterator接口需要实现hasNext()和next()方法C#IEnumerable基于GetEnumerator()方法Rubyeach方法基于块(block)的迭代方式JavaScript的特色在于使用Symbol作为协议键名生成器函数的强大支持与异步编程的良好集成语言层面的语法支持(for...of)17. 迭代器与性能优化优化迭代器性能的几个技巧避免在迭代过程中创建不必要的对象对于数组等线性结构考虑缓存长度使用生成器实现惰性求值避免在热代码路径中使用复杂迭代逻辑// 优化前 class SlowRange { constructor(start, end) { this.start start; this.end end; } [Symbol.iterator]() { return { next: () { if (this.start this.end) { return { value: this.start, done: false }; } return { done: true }; } }; } } // 优化后 class FastRange { constructor(start, end) { this.start start; this.end end; } *[Symbol.iterator]() { let current this.start; const end this.end; while (current end) { yield current; } } }18. 迭代器的链式调用通过组合多个迭代器可以实现强大的数据处理管道function* take(iterable, n) { let count 0; for (const item of iterable) { if (count n) break; yield item; } } function* filter(iterable, predicate) { for (const item of iterable) { if (predicate(item)) { yield item; } } } function* map(iterable, mapper) { for (const item of iterable) { yield mapper(item); } } // 使用示例 const numbers [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const result [ ...take( map( filter(numbers, n n % 2 0), n n * 2 ), 3 ) ]; console.log(result); // [4, 8, 12]19. 迭代器与递归结合迭代器可以很好地处理递归数据结构class FileSystem { constructor(name, isDirectory false) { this.name name; this.isDirectory isDirectory; this.children []; } add(child) { this.children.push(child); return this; } *traverse() { yield this; for (const child of this.children) { if (child.isDirectory) { yield* child.traverse(); } else { yield child; } } } } // 使用示例 const fs new FileSystem(root, true) .add(new FileSystem(dir1, true) .add(new FileSystem(file1.txt)) .add(new FileSystem(file2.txt))) .add(new FileSystem(dir2, true) .add(new FileSystem(file3.txt))); for (const item of fs.traverse()) { console.log(item.name); } // 输出: root, dir1, file1.txt, file2.txt, dir2, file3.txt20. 迭代器在函数式编程中的应用迭代器可以与函数式编程概念完美结合// 惰性求值的函数式操作 function* lazyFilter(iterable, predicate) { for (const item of iterable) { if (predicate(item)) { yield item; } } } function* lazyMap(iterable, mapper) { for (const item of iterable) { yield mapper(item); } } function take(iterable, n) { const result []; const iterator iterable[Symbol.iterator](); let count 0; while (count n) { const { value, done } iterator.next(); if (done) break; result.push(value); } return result; } // 使用示例 const numbers [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const result take( lazyMap( lazyFilter(numbers, n n % 2 0), n n * 2 ), 3 ); console.log(result); // [4, 8, 12]