es-toolkit `iterate` 完全指南:用种子值生成无限惰性迭代器
2026/9/17 2:55:22 网站建设 项目流程

es-toolkititerate完全指南:用种子值生成无限惰性迭代器

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

导读

iterate是 es-toolkit 的es-toolkit/iterator模块提供的核心函数:它接收一个种子值(seed)和一个递推函数,反复把"当前值"变换为"下一个值",从而构造出无限且惰性求值的迭代器。本文以 docs/ja/iterator/reference/iterate.md 为骨架,结合 src/iterator/iterate.ts 的源码与 src/iterator/iterate.spec.ts 的测试,讲解iterate的签名、惰性语义、与原生迭代器助手(taketoArray等)以及takeWhile的组合用法,并覆盖 2 的幂、指数退避重试等待时间等真实场景。读完本文,你将能够在内存占用恒定的前提下安全地消费无限序列。

一、函数签名与基本用法

1.1 签名

const sequence = iterate(seed, getNext);

完整签名如下(见 src/iterator/iterate.ts):

export function iterate<T>(seed: T, getNext: (value: T) => T): IteratorObject<T, undefined>
参数
参数类型说明
seedT序列的第一个值
getNext(value: T) => T从当前值计算下一个值的递推函数
返回值

IteratorObject<T, undefined>:覆盖生成序列的无限惰性迭代器。它携带全部原生迭代器助手方法(mapfiltertakedropflatMapreducetoArray等),可以直接继续链式调用(详见第三节)。

1.2 生成规则

iterate生成的值序列为:

seed, getNext(seed), getNext(getNext(seed)), ...

即第 n 个值由getNext应用 n 次得到。例如:

import { iterate } from 'es-toolkit/iterator'; iterate(1, x => x * 2); // 生成 1, 2, 4, 8, 16, 32, ...

二、源码解读:iterate如何实现惰性与无限

iterate的实现非常精简(src/iterator/iterate.ts):

export function iterate<T>(seed: T, getNext: (value: T) => T): IteratorObject<T, undefined> { let current = seed; let started = false; return iterator(function () { if (started) { current = getNext(current); } else { started = true; } return { value: current, done: false }; }); }

关键点:

  • started标志保证第一次调用next()时直接返回seed本身,之后每调用一次next()才执行一次getNext(current)更新current。这确保了"序列从seed开始"的语义,测试 src/iterator/iterate.spec.ts 中iterate(42, x => x + 1).take(1).toArray()返回[42]验证了这一点。
  • 永不返回done: truenext始终返回{ value: current, done: false },因此该迭代器在概念上是无限的。它必须由taketakeWhile这类短路助手限定范围后才能消费,否则toArray()for...of会无限循环。
  • 惰性求值来自底层 src/iterator/_internal/iterator.ts:iterator()Object.create(Iterator.prototype)包装一个手写的next函数,使其行为与原生迭代器助手返回的值完全一致(一次性、可被Symbol.iterator迭代、携带全部原生方法)。源码注释指出,手写next而非使用生成器函数是刻意的性能选择——直接驱动迭代器协议在同类变换下比yield生成器快约 2 倍。

惰性语义的测试证据

src/iterator/iterate.spec.ts 中的测试用vi.fn间谍验证:

const next = vi.fn((x: number) => x + 1); iterate(0, next); // 只创建,不消费 expect(next).not.toHaveBeenCalled(); // getNext 一次都没有被调用

而消费 3 个值(.take(3).toArray())时,getNext恰好被调用 2 次(seed + 2 次递推产生 3 个值,见 src/iterator/iterate.spec.ts)。这印证了文档中的核心承诺:"迭代器被消费之前不执行任何计算,getNext只运行被取出值的次数"

三、与原生迭代器助手链式使用

iterate返回的IteratorObject以原生Iterator.prototype为原型(src/iterator/_internal/iterator.ts),因此可以直接调用mapfiltertakedropflatMapreducetoArray等原生助手方法,无需额外导入:

import { iterate } from 'es-toolkit/iterator'; // 2 的幂,用 take 限定个数 iterate(1, x => x * 2) .take(5) .toArray(); // 结果: [1, 2, 4, 8, 16]

也可以先map变换再take

iterate(1, x => x * 2) .map(x => x - 1) // 0, 1, 3, 7, ... .take(4) .toArray(); // 结果: [0, 1, 3, 7]

四、用takeWhile以条件限定无限序列

原生助手只有按个数取数的take,没有按条件取数的takeWhile,因此 es-toolkit 在es-toolkit/iterator中补充了takeWhile。它可以安全地按条件切分无限迭代器:shouldContinue返回真值期间持续产出元素,遇到第一个返回假值的元素立即停止(该元素不包含),且剩余元素不再从源迭代器取出。

import { iterate } from 'es-toolkit/iterator'; import { takeWhile } from 'es-toolkit/iterator'; takeWhile( iterate(100, x => x * 2), x => x < 60000 ).toArray(); // 结果: [100, 200, 400, ..., 51200]

实战场景:指数退避的等待时间

文档中给出的经典用法是生成 1 分钟以内的指数退避等待时间——重试逻辑中每次失败后等待时间翻倍:

import { iterate } from 'es-toolkit/iterator'; import { takeWhile } from 'es-toolkit/iterator'; // 1 分钟(60000 ms)以内的指数退避延迟 const backoffDelays = takeWhile( iterate(100, x => x * 2), x => x < 60000 ).toArray(); // [100, 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 51200]

同理可以生成连续日期(每次加一天)或任意递推序列。由于惰性求值,即便序列在概念上无限,内存占用也恒定在 O(1)(仅保存current),只有被消费的部分才会真正计算。

takeWhile的实现要点

takeWhile的源码(src/iterator/takeWhile.ts)体现了与原生助手一致的迭代器关闭协议:当条件不满足提前结束时,通过onClose回调调用source.return?.()关闭上游迭代器,从而保证生成器源中try/finally的清理逻辑(如关闭文件句柄)必然执行。这一点对无限迭代器尤为重要——提前终止时不会留下悬空资源。

五、与其他 es-toolkit/iterator 函数配合

iteratees-toolkit/iterator模块的一员(导出于 src/iterator/index.ts)。该模块的定位是:只补充原生迭代器助手缺失的能力——按个数取数的takedropmapfilter等原生已有,es-toolkit 在其上增加条件式(takeWhiledropWhile)、有状态(scancount)、多源(zipcartesianProduct)等操作,iterate则负责"从种子生成无限序列"。

典型组合:iterate生成无限序列 →dropWhile跳过前缀 →takeWhile/take截断 →toArray物化。由于每个环节都返回IteratorObject,整条流水线始终是惰性的。

六、与pipe(函数式风格)配合

所有es-toolkit/iterator操作都以柯里化形式在es-toolkit/fp/iterator中提供(导出清单见 src/fp/iterator/index.ts)。虽然该导出集中没有iterate(它本身接受种子+递推函数,不适合柯里化),但takeWhiletaketoArray等截断与物化函数均可与pipe组合:

import { pipe } from 'es-toolkit/fp'; import { takeWhile, toArray } from 'es-toolkit/fp/iterator'; pipe( iterate(1, x => x * 2), // 来自 es-toolkit/iterator takeWhile(x => x < 100), toArray() ); // 结果: [1, 2, 4, 8, 16, 32, 64]

七、使用注意事项

  1. 必须先截断再消费iterate返回无限迭代器,直接调用toArray()reduce()for...of会无限循环。任何完整消费前都必须用take(按个数)或takeWhile(按条件)限定范围。
  2. 一次性消费:与所有 JavaScript 迭代器相同,结果只能消费一次;消费完毕后不再产生任何值。若需复用序列,请重新调用iterate或先toArray()物化。
  3. 选择合适模块:如果数据已在数组中且需要整体处理,使用es-toolkit的数组函数即可;当输入规模大、可能无限、流水线可能提前结束,或数据本身已是迭代器/生成器时,才选用es-toolkit/iterator(参见 docs/ja/iterator/intro.md)。

小结

  • iterate(seed, getNext)以 O(1) 内存生成无限惰性序列:seed, getNext(seed), getNext(getNext(seed)), ...
  • 返回值是携带全部原生助手方法的IteratorObject,可无缝链式调用takemaptoArray等。
  • 无限序列必须由take(个数)或takeWhile(条件)限定后才能消费;takeWhile会正确关闭上游迭代器。
  • 典型应用:2 的幂、连续日期、指数退避重试等待时间等递推序列。

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询