尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JavaScript可迭代对象与迭代器协议详解

JavaScript可迭代对象与迭代器协议详解 1. 为什么需要让对象可迭代在JavaScript中for...of循环是一种简洁高效的遍历方式但默认只能用于数组、字符串等内置可迭代对象。当我们尝试对普通对象使用for...of时会直接抛出对象不可迭代的错误。这其实暴露了JavaScript语言设计中的一个重要特性——可迭代协议Iterable Protocol。注意在ES6之前我们只能用for...in遍历对象的键但这种方式会包含原型链上的属性且遍历顺序不保证远不如for...of直观可靠。让自定义对象支持迭代的核心价值在于统一数据访问接口无论是数组、集合还是自定义数据结构都可以用相同的语法遍历实现惰性计算迭代器可以按需生成值节省内存支持所有消费迭代器的语法包括...扩展运算符、Array.from()等2. 可迭代协议与迭代器协议详解2.1 可迭代协议Iterable Protocol一个对象要成为可迭代对象必须实现iterator方法即Symbol.iterator属性。这个方法必须返回一个迭代器对象。当对象被for...of消费时会自动调用这个方法。const myIterable { [Symbol.iterator]: function() { // 返回迭代器对象 } }2.2 迭代器协议Iterator Protocol迭代器对象必须实现next()方法该方法返回包含两个属性的对象value当前迭代的值done布尔值表示迭代是否结束{ next: function() { return { value: any, // 当前值 done: boolean // 是否结束 } } }3. 实现自定义可迭代对象3.1 基础实现示例让我们创建一个简单的范围迭代器生成从start到end的数字序列class Range { constructor(start, end) { this.start start this.end end } [Symbol.iterator]() { let current this.start const end this.end return { next() { if (current end) { return { value: current, done: false } } else { return { done: true } } } } } } // 使用示例 for (const num of new Range(1, 5)) { console.log(num) // 依次输出1,2,3,4,5 }3.2 更复杂的树形结构迭代对于复杂数据结构迭代器可以按特定顺序遍历。以下是一个二叉树的中序遍历实现class TreeNode { constructor(value, left null, right null) { this.value value this.left left this.right right } *[Symbol.iterator]() { if (this.left) yield* this.left yield this.value if (this.right) yield* this.right } } // 构建树 const tree new TreeNode(4, new TreeNode(2, new TreeNode(1), new TreeNode(3) ), new TreeNode(5) ) // 中序遍历 for (const val of tree) { console.log(val) // 输出1,2,3,4,5 }技巧使用生成器函数function*可以大大简化迭代器实现它会自动返回符合迭代器协议的对象。4. 高级迭代技巧与应用场景4.1 无限序列迭代器迭代器不一定要有终点可以实现无限序列function fibonacci() { let [prev, curr] [0, 1] return { [Symbol.iterator]() { return this }, next() { [prev, curr] [curr, prev curr] return { value: curr, done: false } } } } // 使用take限制数量 function* take(iterable, n) { for (const item of iterable) { if (n-- 0) return yield item } } // 获取前10个斐波那契数 for (const num of take(fibonacci(), 10)) { console.log(num) }4.2 异步迭代器ES2018对于异步数据源可以实现异步迭代协议const asyncIterable { [Symbol.asyncIterator]() { let i 0 return { next() { if (i 5) { return Promise.resolve({ value: i, done: false }) } return Promise.resolve({ done: true }) } } } } ;(async function() { for await (const item of asyncIterable) { console.log(item) // 0,1,2,3,4 } })()5. 常见问题与解决方案5.1 迭代器复用问题同一个迭代器通常只能遍历一次再次遍历需要重新获取迭代器const range new Range(1, 3) const it range[Symbol.iterator]() console.log(it.next().value) // 1 console.log(it.next().value) // 2 // 再次使用同一个迭代器 for (const num of it) { console.log(num) // 只输出3因为前两个值已经被消费 } // 正确做法是每次for...of都获取新迭代器 for (const num of range) { console.log(num) // 1,2,3 }5.2 手动调用迭代器有时需要手动控制迭代过程const iterable { data: [10, 20, 30], [Symbol.iterator]() { let index 0 return { next: () { if (index this.data.length) { return { value: this.data[index], done: false } } return { done: true } } } } } const iterator iterable[Symbol.iterator]() let result iterator.next() while (!result.done) { console.log(result.value) result iterator.next() }5.3 与生成器函数的配合生成器函数会自动返回符合迭代器协议的对象极大简化实现const obj { *[Symbol.iterator]() { yield 1 yield 2 yield 3 } } for (const num of obj) { console.log(num) // 1,2,3 }6. 实际应用场景6.1 自定义集合类实现类似Java的集合类迭代class Collection { constructor() { this.items [] } add(item) { this.items.push(item) return this } *[Symbol.iterator]() { for (const item of this.items) { yield item } } } const collection new Collection() collection.add(a).add(b).add(c) for (const item of collection) { console.log(item) // a,b,c }6.2 分页数据获取模拟API分页数据的迭代function createPager(total, pageSize 10) { let currentPage 0 const totalPages Math.ceil(total / pageSize) return { [Symbol.iterator]() { return { next() { if (currentPage totalPages) { // 模拟API请求 const start currentPage * pageSize const end start pageSize const data Array.from({length: pageSize}, (_, i) start i 1) currentPage return { value: data, done: false } } return { done: true } } } } } } for (const page of createPager(35, 10)) { console.log(Page:, page) // Page: [1,2,...,10] // Page: [11,12,...,20] // Page: [21,22,...,30] // Page: [31,32,...,35] }6.3 状态机迭代用迭代器实现状态机function* stateMachine() { let state start while (true) { switch (state) { case start: console.log(Starting...) state running yield break case running: console.log(Running...) state paused yield break case paused: console.log(Paused...) state stopped yield break case stopped: console.log(Stopped.) return } } } const machine stateMachine() machine.next() // Starting... machine.next() // Running... machine.next() // Paused... machine.next() // Stopped.7. 性能考量与最佳实践7.1 迭代器性能虽然迭代器提供了优雅的抽象但在性能关键路径上需要注意每次迭代都会创建新的迭代器对象yield操作有一定开销在V8引擎中手写迭代器比生成器函数性能稍好7.2 内存管理对于大型数据集考虑惰性计算不要预先生成所有数据及时释放不再需要的迭代器引用对于无限迭代器确保有终止条件7.3 最佳实践总结优先使用生成器函数简化实现为迭代器添加return()和throw()方法实现完整协议考虑实现[Symbol.asyncIterator]支持异步场景文档中明确说明对象的迭代顺序和规则对于性能敏感场景提供备用的直接访问方法在实现自定义迭代时我发现最常遇到的坑是忘记迭代器是有状态的。一个常见的错误是在多个地方复用同一个迭代器实例导致意外的行为。正确的做法是每次迭代都通过[Symbol.iterator]()获取新的迭代器。
返回列表