es-toolkit 迭代器 range 完全指南:不分配数组的懒加载数字序列生成器
es-toolkit 迭代器 range 完全指南不分配数组的懒加载数字序列生成器【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkites-toolkit/iterator的range是生成数字序列的懒加载迭代器与es-toolkit/math中直接返回数组的range不同它在迭代器被真正消费之前不会计算任何数字也不分配中间数组因此可以表达range(0, Infinity)这样的无限计数器再配合原生迭代器助手take、map、toArray等进行链式处理。读完本文你将掌握range的三种调用形式、参数语义、错误约束、源码级实现原理以及在分页、索引生成、无限序列等场景下的实战用法。range 是什么一段话理解核心价值range以固定步长step懒加载地生成一段数字序列。它的典型使用场景是需要遍历一段连续整数、但不想为它们分配一个完整的数组——尤其是当序列很长甚至无限时数组版range无法胜任而迭代器版可以按需逐个产出。const numbers range(end); const numbers range(start, end); const numbers range(start, end, step);使用range(0, Infinity)编写无上限计数器是它区别于数组版最直观的例子数组版根本不可能为无穷多个数字分配内存而迭代器版只有在被消费时才产出下一个数字。三种调用形式与基本用法range支持一至三个参数行为与原生Array.from({ length }, (_, i) ...)思路一致但全部是懒求值import { range } from es-toolkit/iterator; // 从 0 数到 end不含。 range(4).toArray(); // 结果: [0, 1, 2, 3] // 指定起始与终止。 range(1, 4).toArray(); // 结果: [1, 2, 3] // 指定步长负步长可以递减。 range(0, 20, 5).toArray(); // 结果: [0, 5, 10, 15] range(0, -4, -1).toArray(); // 结果: [0, -1, -2, -3] // 无限计数器用 take 限定边界。 range(0, Infinity).take(3).toArray(); // 结果: [0, 1, 2]这些示例同时也是 src/iterator/range.spec.ts 中测试用例的直接写照覆盖了单参数、双参数、自定义步长、负步长、空区间、无限序列与错误参数等全部行为。参数详解参数类型含义默认值startnumber范围的起始数字包含仅传一个参数时默认为0endnumber范围的结束数字不包含无stepnumber可选数字之间的步长必须是非零整数1关键语义要点end是排他的exclusiverange(4)产出0, 1, 2, 3不包含4range(1, 4)产出1, 2, 3。start是包含的inclusive序列从start本身开始。step可以为负range(0, -4, -1)产出0, -1, -2, -3用于递减序列。方向自动判断步长为正时按递增方向比较步长为负时按递减方向比较详见下文源码分析。空区间合法range(0)与range(5, 5)都返回空序列测试用例 src/iterator/range.spec.ts 中对此有显式断言。返回值携带全部原生迭代器助手的 IteratorObjectrange的返回值类型是IteratorObjectnumber, undefined。这是一个原型指向原生Iterator.prototype的迭代器对象因此它天然具备 JavaScript 内置迭代器助手的全部能力map、filter、take、drop、flatMap、reduce、toArray等都可以直接链式调用无需额外引入工具函数。range(1, 11) .filter(x x % 2 0) // [2, 4, 6, 8, 10] .map(x x * x) // [4, 16, 36, 64, 100] .toArray();关于这一点src/iterator/_internal/iterator.ts 的注释给出了明确的实现承诺返回结果与内置迭代器助手如array.values().map(...)的行为完全一致——它是单次消费的single-shot可以通过Symbol.iterator再次迭代自身并且携带所有原生辅助方法。与 es-toolkit/math 的 range 对比何时选哪个es-toolkit同时提供两个range它们解决的是不同量级的问题维度es-toolkit/math的 rangees-toolkit/iterator的 range返回类型number[]立即分配数组IteratorObjectnumber, undefined懒求值计算时机调用时立即算出全部数字消费时才逐个产出无限序列不支持支持如range(0, Infinity)典型场景数据量有限、需要数组结果大序列、无限序列、提前终止的管道es-toolkit/iterator 介绍文档 给出了选择建议当数据已经是数组且需要整体处理时es-toolkit的数组函数是更合适的默认选择当输入很大或无限、管道可能提前结束、或数据本身就以迭代器/生成器形式存在时应该选用es-toolkit/iterator。无限序列与提前终止range 的核心优势懒加载意味着「不被请求就不计算」。range(0, Infinity)虽然名义上是从 0 到正无穷但如果没有消费者它一个数字都不会产出一旦配合短路型助手如原生take它就是一个实用的无上限计数器// 前 5 个偶数的平方 range(0, Infinity) .filter(x x % 2 0) .map(x x * x) .take(5) .toArray(); // 结果: [0, 4, 16, 36, 64]类似的无限序列思想在迭代器模块中是一致的iterate同样支持通过take/takeWhile限定无限序列的边界。这也与整个模块「只做原生助手缺失的事情」的设计哲学相符——take等原生已有range负责补齐原生没有的谓词式、有状态式与多源操作。源码级实现原理range的实现位于 src/iterator/range.ts逻辑非常紧凑值得逐行理解export function range(start: number, end?: number, step 1): IteratorObjectnumber, undefined { if (end null) { end start; start 0; } if (!Number.isInteger(step) || step 0) { throw new Error(The step value must be a non-zero integer, but got ${step}.); } const finalEnd end; let current start; return iterator(function () { if (step 0 ? current finalEnd : current finalEnd) { return { value: undefined, done: true }; } const value current; current step; return { value, done: false }; }); }实现要点参数归一化当end null即只传一个参数时把end赋为start、start归零对应「从 0 开始」的语义step通过默认参数固定为1。严格校验用Number.isInteger(step)保证步长必须是整数且step 0会被拒绝——这确保了序列必然单调推进不会陷入死循环。方向自适应step 0时以current finalEnd作为终止条件递增否则以current finalEnd终止递减。因此range(0, -4, -1)能正确产出0, -1, -2, -3。闭包状态机current保存在闭包中每次调用next时先判断是否越界再返回当前值并推进步长整个过程不产生任何数组。值得强调的是底层iterator包装器src/iterator/_internal/iterator.ts的设计它通过Object.create(Iterator.prototype)创建对象将手写的next函数包装成行为等同原生迭代器助手的对象。这是刻意的性能取舍——源码注释指出直接驱动迭代器协议比基于yield的生成器大约快两倍而Object.create(Iterator.prototype)相比普通对象字面量没有可测量的开销。同时它遵循 IteratorClose 协议当消费者提前终止take触达上限、for...of中break、或next抛错时onClose恰好执行一次确保上游资源如生成器源中的try/finally被可靠释放。错误处理当step不是非零整数时range会抛出Error。具体触发条件有两类step 0步长为零会导致序列永远无法越过end陷入死循环因此被禁止。Number.isInteger(step) false如1.5这类非整数步长无法保证序列按预期推进同样被禁止。测试用例 src/iterator/range.spec.ts 中对此有验证expect(() range(0, 10, 0)).toThrow(); expect(() range(0, 10, 1.5)).toThrow();错误消息会包含实际传入的非法值The step value must be a non-zero integer, but got ${step}.便于定位问题。单次消费语义Single-shot与所有 JavaScript 迭代器一样range返回的结果是单次消费的一旦被完整消费再次迭代将不再产生任何值。测试用例对此有明确验证const it range(3); expect(it.toArray()).toEqual([0, 1, 2]); expect(it.toArray()).toEqual([]); // 已消费返回空这是迭代器模型的固有行为而非缺陷。如果需要对同一序列重复消费请在每次使用时重新调用range。更深层的保障在于iterator包装器实现了 IteratorClose 协议——即使管道提前终止例如take提前截断迭代器也会被正确关闭后续调用一律返回done: true不会出现资源泄漏或意外继续产出。实战组合与其他迭代器函数协同range是 es-toolkit/iterator 模块的一员该模块提供了cartesianProduct、chunk、count、dropWhile、head、iterate、partition、scan、takeWhile、uniqBy、zip等谓词式、有状态式和多源操作。range可以自然地和它们组合例如生成分页索引并按页切块import { range, chunk } from es-toolkit/iterator; // 100 条数据每页 10 条 → 10 页的页码序列 const pages range(1, 11).toArray(); // [1, 2, ..., 10] // 对 0..99 按每 10 个一组分批 const batches chunk(range(100), 10).toArray();如果你在使用函数式管道整个迭代器模块还提供es-toolkit/fp/iterator的柯里化版本可与pipe配合使用参见 es-toolkit/iterator 介绍 中的 pipe 示例。延伸阅读英文版 range 参考文档、日文版本文依据数组版 range需要数组结果时的选择es-toolkit/iterator 模块介绍懒加载设计哲学与使用场景总览iterate 参考文档另一个无限序列生成器按递推关系而非定步长生成源码实现与底层 iterator 包装器单元测试覆盖全部边界行为的可运行示例【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考