ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

open-slide 前端性能实践:将多次数组 filter/map 迭代合并为单次循环(js-combine-iterations 规则详解)

open-slide 前端性能实践:将多次数组 filter/map 迭代合并为单次循环(js-combine-iterations 规则详解) 【免费下载链接】open-slideA slide framework built for agents.项目地址https://gitcode.com/gh_mirrors/op/open-slide点击查看免费下载导读在 React/Next.js 应用中filter()、map()等数组方法每次调用都会完整遍历一次数组多个方法串联意味着同一份数据被反复扫描、反复分配中间数组。本篇以 open-slide 仓库内.agents/skills/vercel-react-best-practices技能包中的 js-combine-iterations 规则 为骨架深入讲解把多次数组迭代合并为单次循环的优化手法先剖析多次遍历的真实成本时间复杂度与中间数组分配再给出可运行的 TypeScript 正反示例最后结合 open-slide 源码中的真实调用点说明这条规则如何在开发中落地。读完本篇你将掌握在渲染路径、数据处理等热路径中识别冗余遍历、并用单次循环一次到位的能力。规则出处Vercel React 最佳实践技能包中的 JavaScript 性能分类open-slide 仓库在.agents/skills/下内置了多个面向 AI Agent 与 LLM 的编码技能包。其中 vercel-react-best-practices 由 Vercel 维护是一套按影响等级排序的 React/Next.js 性能优化指南共 70 条规则、8 个分类这些规则用于指导 Agent 在编写、评审或重构 React/Next.js 代码时自动应用最佳实践。根据 SKILL.md 的分类表8 个分类按优先级排序如下优先级分类影响等级文件名前缀1Eliminating WaterfallsCRITICALasync-2Bundle Size OptimizationCRITICALbundle-3Server-Side PerformanceHIGHserver-4Client-Side Data FetchingMEDIUM-HIGHclient-5Re-render OptimizationMEDIUMrerender-6Rendering PerformanceMEDIUMrendering-7JavaScript PerformanceLOW-MEDIUMjs-8Advanced PatternsLOWadvanced-本篇讨论的js-combine-iterations属于第 7 类 JavaScript Performance其 frontmatter 声明title: Combine Multiple Array Iterationsimpact: LOW-MEDIUMimpactDescription: reduces iterations减少迭代次数tags: javascript, arrays, loops, performance分类描述指出针对热路径hot paths的微优化可以累积出有意义的改进见 _sections.md 第 7 节。这正是理解这条规则影响力的关键单次合并的收益看似微小但在高频执行的渲染、过滤、汇总逻辑中反复出现时能显著降低 CPU 与 GC 压力。规则核心多个.filter()/.map()调用会多次遍历数组规则原文的定义只有一句话却点出了本质Multiple.filter()or.map()calls iterate the array multiple times. Combine into one loop.即多个独立的.filter()或.map()调用会对同一个数组执行多次完整遍历。优化的方向是把它们合并进一个循环让数据只被扫描一次。复杂度分析为什么多次遍历是浪费对长度为n的数组执行k个独立的filter/map调用时间成本每次调用都是O(n)总计O(k·n)合并为单次循环后降为O(n)。空间成本每个.filter()/.map()都会产生一个新的中间数组k次调用产生k个或更多中间分配合并后不再产生这些中间数组。缓存与 GC 压力反复扫描同一段内存区域、反复分配短期数组会增加垃圾回收频率。在 React 组件的 render 过程中执行这些操作每次渲染都会产生新的垃圾放大了对帧率与首屏性能的影响。正因为收益是线性级别、随数组规模与调用次数增长规则将其标为 LOW-MEDIUM 而非 CRITICAL——它属于锦上添花的微优化而非瀑布流式请求那样的头号性能杀手。问题代码三次遍历的三个独立过滤规则给出的错误示例3 次迭代如下const admins users.filter(u u.isAdmin) const testers users.filter(u u.isTester) const inactive users.filter(u !u.isActive)这段代码存在两个问题遍历了 3 次users数组被完整扫描了 3 遍时间复杂度是3·O(n)。分配了 3 个中间数组每次.filter()都新建数组即使users本身很大也只有在全部过滤完成后才能释放。注意这里的三个过滤条件是相互独立的谓词predicate且同一个用户可能同时满足多个条件例如既是 admin 又是 tester最终产出的是三个不同的结果集合。这种一进多出的分组场景恰恰是多次 filter 写法最容易被写成重复遍历的典型。正确做法单次循环一次遍历、按条件分流规则给出的正确示例1 次迭代const admins: User[] [] const testers: User[] [] const inactive: User[] [] for (const user of users) { if (user.isAdmin) admins.push(user) if (user.isTester) testers.push(user) if (!user.isActive) inactive.push(user) }要点拆解只遍历一次users只被扫描一遍时间复杂度降为O(n)。显式类型保持完整在 TypeScript 下const admins: User[] []的显式类型注解让三个结果数组的类型安全与原.filter()写法完全等价不会因为改用循环而丢失类型推断。条件相互独立三个if互不排斥同一元素可同时落入多个桶行为与原三段filter完全一致。可读性未受损for...of配合清晰的结果变量名语义与筛出 admin / tester / inactive完全对应。如果追求更函数式的写法也可以用reduce实现同样的单次遍历const { admins, testers, inactive } users.reduce{ admins: User[] testers: User[] inactive: User[] }( (acc, user) { if (user.isAdmin) acc.admins.push(user) if (user.isTester) acc.testers.push(user) if (!user.isActive) acc.inactive.push(user) return acc }, { admins: [], testers: [], inactive: [] }, )但从可读性看规则的for...of版本更直白reduce适合在已有函数式代码风格中保持一致。变体链式 filter / mapfilter / 单一结果场景js-combine-iterations的合并思路可以推广到几种常见变体。变体一链式.filter().filter()条件需全部满足// 3 次遍历先筛 isAdmin再筛 isActive const result users .filter(u u.isAdmin) .filter(u u.isActive)等价合并为单次遍历const result users.filter(u u.isAdmin u.isActive)变体二.map()后再.filter()过滤的同时变换技能包中的配套规则 js-flatmap-filter.md 专门处理这一场景链式.map().filter(Boolean)会创建中间数组并遍历两次改用.flatMap()一次完成变换 过滤// 2 次迭代 中间数组 const userNames users .map(user user.isActive ? user.name : null) .filter(Boolean) // 1 次迭代无中间数组 const userNames users.flatMap(user user.isActive ? [user.name] : [] )变体三连续多个.map()纯变换流水线// 2 次遍历 2 个中间数组 const result arr.map(f).map(g)当g的输入不需要f的全部输出、且不需要中间结果时可合并为arr.map(x g(f(x)))。不过若中间结果被多处复用保留拆分更清晰——合并的前提是中间数组无复用价值。何时不应合并只产出单一结果单个.filter()本身就是一次遍历无需改循环。不同数组、不同来源的数据规则针对的是同一个数组被多次遍历如果各 filter 作用于不同数据源合并毫无意义。条件极其复杂、拆开更易读性能优化不应以可读性崩塌为代价只有当数组规模较大、调用链较长或位于热路径时才值得合并。数组会被后续代码修改单次循环得到的是可变数组若下游依赖不可变语义需保留.filter()的新数组语义或显式拷贝。仓库中的真实案例这条规则对应的代码场景open-slide 源码中能找到多处与多次数组迭代相关的实现可对照规则验证其适用性。案例一排列面板中同一帧数组被反复 map在 arrange-panel.tsxopen-slide 可视化编辑器的排列面板中useEffect内对selection与frames进行了多轮变换const anchors selection .map((target) target.anchor) .filter((anchor) anchor.isConnected); const frames anchors.map((anchor) readFrame(anchor, canvas)); const x Math.min(...frames.map((frame) frame.x)); const y Math.min(...frames.map((frame) frame.y)); setFrame({ x, y, width: Math.max(...frames.map((frame) frame.x frame.width)) - x, height: Math.max(...frames.map((frame) frame.y frame.height)) - y, ... });从源码结构看这里对frames数组先后进行了 4 次.map()求 x、求 y、求宽度、求高度每次都会新建数组并完整遍历。若所选元素数量很大或该 effect 因opsVersion变更而频繁触发这 4 次遍历与 4 个中间数组就会成为js-combine-iterations规则的典型适用对象——完全可以改成单次for...of循环里同时累积minX、minY、maxRight、maxBottom四个极值正如技能包中另一条规则 js-min-max-loop 所建议的求 min/max 用循环而非Math.min(...)展开。案例二幻灯片模块发现时对同一数组做互补过滤在 open-slide-plugin.tsVite 插件中生成幻灯片模块的入口函数中scanned数组被过滤了两次const entries scanned.filter((e) SLIDE_ID_RE.test(e.id)); const ignored scanned.filter((e) !SLIDE_ID_RE.test(e.id)).map((e) e.id);这里同样是对同一个数组执行两次.filter()谓词互为补集再对第二次的结果做.map()。按照js-combine-iterations的思路可以合并为单次遍历、一次循环内同时填充entries与ignoredconst entries: typeof scanned [] const ignored: string[] [] for (const e of scanned) { if (SLIDE_ID_RE.test(e.id)) { entries.push(e) } else { ignored.push(e.id) } }需要说明的是该处源码特意用注释解释了只保留合法 id、将非法 id 单独列出并告警的业务意图拆分写法的可读性本身也成立——这恰好印证了规则的边界合并是性能优化而非强制规范当可读性收益更大时保留原写法是合理取舍。相关规则组合使用如何与同类 js 优化协同js-combine-iterations并非孤立存在同属 JavaScript Performance 分类的规则可以组合使用形成一套完整的数组热路径优化工具箱完整清单见 SKILL.md 第 7 节js-flatmap-filter.map().filter(Boolean)改为.flatMap()消除中间数组上文变体二已演示。js-index-maps在循环/过滤前先new Map(users.map(u [u.id, u]))建立索引把反复find/includes的O(n)查找降为O(1)。js-set-map-lookupsitems.filter(item allowedIds.includes(item.id))改为先建Set再allowedIds.has(item.id)避免每次过滤都做线性查找。js-length-check-first在昂贵比较前先检查array.length与单次循环结合可进一步减少无效遍历。js-cache-property-access循环内反复读取的对象属性先缓存到局部变量。典型组合姿势是先用js-index-maps/js-set-map-lookups把查找类操作从O(n)降为O(1)再用js-combine-iterations把同一数组上的多轮遍历合并成一轮。两条规则互补共同压低热路径的时间与 GC 成本。如何让 Agent 在 open-slide 开发中应用这条规则open-slide 仓库为 Agent 内置了这套技能包意味着在编写、评审或重构 React/Next.js 代码时Agent 会依据这些规则文件每条规则文件包含为什么重要 错误示例 正确示例 扩展上下文结构模板见 _template.md自动检查是否出现对同一数组的多次.filter()/.map()调用是否生成了可避免的中间数组合并为单次循环后类型与行为是否保持等价是否属于为优化而牺牲可读性的过度设计。当你评审自己或他人提交的前端代码时也可以用同样的检查清单人工过一遍看到对同一数组连续调用两个及以上数组方法、且各步互不依赖时就值得停下来问一句——能不能一趟走完这条规则给出的是一个低成本、低风险、可机械执行的答案能合并就合并不能合并就保持清晰。总结规则本质多个.filter()/.map()调用对同一数组产生多次遍历O(k·n)与多个中间数组合并为单次for...of或reduce循环后降为O(n)且零中间分配。影响等级LOW-MEDIUM属于热路径微优化适合批量应用于高频数据处理与渲染逻辑。适用边界独立谓词分组、链式过滤、mapfilter 变换均可合并单一结果、多数据源、可读性优先时不强求。仓库实证open-slide 的 arrange-panel.tsx 与 open-slide-plugin.ts 中均可找到对应场景可对照规则逐步优化。配套规则与js-flatmap-filter、js-index-maps、js-set-map-lookups组合使用效果更完整。本规则原文与更多 70 条性能规则位于 rules/js-combine-iterations.md 与 vercel-react-best-practices/SKILL.md可在 open-slide 仓库内直接查阅使用。赞分享【免费下载链接】open-slideA slide framework built for agents.项目地址https://gitcode.com/gh_mirrors/op/open-slide点击查看免费下载相关推荐ZCode 性能规则详解合并多次数组遍历js-combine-iterations——用单次循环替代多次 filter/mapZCode 性能规则详解合并多次数组遍历js combine iterations——用单次循环替代多次 filter/map 本篇技术指南以 ZCodeopen-agents 中的 js-combine-iterations 规则把多次数组遍历合并为单循环的 JavaScript 性能实践open agents 中的 js combine iterations 规则把多次数组遍历合并为单循环的 JavaScript 性能实践 本文基于 open人工智能AI Agent代码智能体Agent 工作流Agent 沙箱工具调用后端前端VoiceCraft 语音克隆与语音编辑实战指南VoiceCraft 语音克隆与语音编辑实战指南 VoiceCraft 是一个开源的零样本文本转语音TTS与语音编辑工具它把参考音频和文字都转成令牌人工智能大模型语音音频上一篇Windows版本无缝转换CMWTAT_Digital_Edition多版本激活与升级教程下一篇NcmpGui终极指南5步轻松解锁网易云音乐加密格式创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表