如何通过Symbol.iterator实现数值序列的按需延迟加载处理?
- 内容介绍
- 文章标签
- 相关推荐
本文共计897个文字,预计阅读时间需要4分钟。
Symbol.iterator无法直接用于原始数值(如number类型)等,因为原始数值没有属性、无法调用挂载方法,也不具备迭代器接口。但可以通过包装数值范围(如start...end)创建一个可迭代的对象,从而支持`for...of`、展开运算符、解构等语法,并实现真正的`*`操作。
用对象封装范围,返回自定义迭代器
核心思路是:定义一个类或工厂函数,接收起始、结束、步长等参数,其 [Symbol.iterator]() 方法返回一个迭代器对象(含 next()),每次调用只计算下一个值,不预先生成整个数组。
- 迭代器内部维护当前状态(如
current),next()每次递进并返回{ value, done } - 支持正向/负向步长,自动判断终止条件(
<=或>=) - 不占用额外内存存储所有数字,适合超大范围(如
range(0, 1e12))
示例:
<!-- 简洁可复用的 range 实现 -->function range(start, end, step = 1) { return { [Symbol.iterator]() { let current = start; const direction = step > 0 ? 1 : -1; return { next() { if ((direction > 0 && current <= end) || (direction < 0 && current >= end)) { const value = current; current += step; return { value, done: false }; } return { value: undefined, done: true }; } }; } }; } <p>// 使用 for (const n of range(1, 5)) { console.log(n); // 1, 2, 3, 4, 5(按需产出,非一次性生成数组) }</p><p>console.log([...range(0, 4, 2)]); // [0, 2, 4]
支持中断、复用与组合的进阶写法
真实场景中常需「取前 N 个」「跳过 M 个」「过滤偶数」等操作。借助生成器函数(function*)可更自然地表达惰性逻辑,且天然支持 break、return 和 yield 控制流。
- 生成器函数自动实现
Symbol.iterator,无需手动写next - 可配合
take、skip、filter等高阶函数做管道式处理(返回新迭代器) - 每个操作仍保持懒执行——只有被消费时才触发计算
示例(带 take 的链式调用):
function* range(start, end, step = 1) { let current = start; const inc = () => { current += step; }; while ((step > 0 && current <= end) || (step < 0 && current >= end)) { yield current; inc(); } } <p>function* take(iter, n) { let count = 0; for (const item of iter) { if (count >= n) break; yield item; count++; } }</p><p>// 取前 3 个偶数:range(0,100) → filter → take(3) const evensUpTo3 = take( (function*() { for (const x of range(0, 100)) if (x % 2 === 0) yield x; })(), 3 ); console.log([...evensUpTo3]); // [0, 2, 4]
注意边界与类型安全
原始数值序列看似简单,但实际易踩坑:
-
浮点步长误差:避免用
0.1步长遍历(如range(0, 1, 0.1)),改用整数缩放后除法(for (let i = 0; i ) -
无限序列需显式终止:若省略
end,必须靠外部break或take控制,否则for...of会卡死 -
不可重复遍历:默认迭代器是一次性的;如需多次使用,每次调用
[Symbol.iterator]()应返回新迭代器实例(如 new Range(...))
不复杂但容易忽略
本文共计897个文字,预计阅读时间需要4分钟。
Symbol.iterator无法直接用于原始数值(如number类型)等,因为原始数值没有属性、无法调用挂载方法,也不具备迭代器接口。但可以通过包装数值范围(如start...end)创建一个可迭代的对象,从而支持`for...of`、展开运算符、解构等语法,并实现真正的`*`操作。
用对象封装范围,返回自定义迭代器
核心思路是:定义一个类或工厂函数,接收起始、结束、步长等参数,其 [Symbol.iterator]() 方法返回一个迭代器对象(含 next()),每次调用只计算下一个值,不预先生成整个数组。
- 迭代器内部维护当前状态(如
current),next()每次递进并返回{ value, done } - 支持正向/负向步长,自动判断终止条件(
<=或>=) - 不占用额外内存存储所有数字,适合超大范围(如
range(0, 1e12))
示例:
<!-- 简洁可复用的 range 实现 -->function range(start, end, step = 1) { return { [Symbol.iterator]() { let current = start; const direction = step > 0 ? 1 : -1; return { next() { if ((direction > 0 && current <= end) || (direction < 0 && current >= end)) { const value = current; current += step; return { value, done: false }; } return { value: undefined, done: true }; } }; } }; } <p>// 使用 for (const n of range(1, 5)) { console.log(n); // 1, 2, 3, 4, 5(按需产出,非一次性生成数组) }</p><p>console.log([...range(0, 4, 2)]); // [0, 2, 4]
支持中断、复用与组合的进阶写法
真实场景中常需「取前 N 个」「跳过 M 个」「过滤偶数」等操作。借助生成器函数(function*)可更自然地表达惰性逻辑,且天然支持 break、return 和 yield 控制流。
- 生成器函数自动实现
Symbol.iterator,无需手动写next - 可配合
take、skip、filter等高阶函数做管道式处理(返回新迭代器) - 每个操作仍保持懒执行——只有被消费时才触发计算
示例(带 take 的链式调用):
function* range(start, end, step = 1) { let current = start; const inc = () => { current += step; }; while ((step > 0 && current <= end) || (step < 0 && current >= end)) { yield current; inc(); } } <p>function* take(iter, n) { let count = 0; for (const item of iter) { if (count >= n) break; yield item; count++; } }</p><p>// 取前 3 个偶数:range(0,100) → filter → take(3) const evensUpTo3 = take( (function*() { for (const x of range(0, 100)) if (x % 2 === 0) yield x; })(), 3 ); console.log([...evensUpTo3]); // [0, 2, 4]
注意边界与类型安全
原始数值序列看似简单,但实际易踩坑:
-
浮点步长误差:避免用
0.1步长遍历(如range(0, 1, 0.1)),改用整数缩放后除法(for (let i = 0; i ) -
无限序列需显式终止:若省略
end,必须靠外部break或take控制,否则for...of会卡死 -
不可重复遍历:默认迭代器是一次性的;如需多次使用,每次调用
[Symbol.iterator]()应返回新迭代器实例(如 new Range(...))
不复杂但容易忽略

