☰
30 seconds of code 数组技巧:用 findIndex + splice + push 实现替换或追加数组值
2026/10/5 3:18:57 网站建设 项目流程
  • 教程
  • 文档

【免费下载链接】30-seconds-of-code

Coding articles to level up your development skills

项目地址:https://gitcode.com/gh_mirrors/30/30-seconds-of-code
点击查看免费下载

在处理对象数组时,经常需要"若已存在则替换、不存在则追加"的幂等更新逻辑。本文基于 30 seconds of code 仓库中的 replace-or-append-array-value.md 片段,完整讲解replaceOrAppend工具函数的实现原理、比较函数设计、浅拷贝与不可变更新策略,并结合仓库源码给出可直接复制的实战代码与边界讨论。

为什么需要"替换或追加"数组值

在处理 JavaScript 数组时,尤其是数组元素是对象的场景下,常常会遇到这样的需求:往数组里写入一条数据,如果已存在(按某个唯一标识判断)就更新原位置的值,如果不存在就在末尾追加。典型场景包括:

  • 维护用户列表,按id或name作为唯一键做幂等写入;
  • 表单编辑后的数据落库,避免同一行数据被重复添加;
  • 实时推送数据的去重合并,按业务主键合并新旧状态。

从直觉上看,这个需求并不复杂,但实现时很容易踩坑:直接对原数组做push或赋值操作会原地修改数组;用indexOf判断对象是否相等又因为引用比较而失效。为此,30 seconds of code 的 replace-or-append-array-value.md 给出了一种简洁、通用的实现,本文将其展开并深入拆解。

核心思路:三步走的算法设计

原文档给出的算法可以归纳为三步:

  1. 判定存在性:使用Array.prototype.findIndex()配合一个比较函数compFn找到目标项在数组中的下标;
  2. 分支处理:若下标为-1(不存在),用Array.prototype.push()追加到末尾;否则用Array.prototype.splice()在原下标处替换;
  3. 返回新数组:函数开头先用扩展运算符...创建原数组的浅拷贝,所有修改都作用在拷贝上,最后返回新数组,保证原数组不被污染。

完整实现与逐行解析

以下是原文档中的完整实现(位于 replace-or-append-array-value.md):

const replaceOrAppend = (arr, val, compFn) => { const res = [...arr]; const i = arr.findIndex(v => compFn(v, val)); if (i === -1) res.push(val); else res.splice(i, 1, val); return res; };

逐行拆解如下:

代码作用与说明
const res = [...arr]用扩展运算符创建arr的浅拷贝,后续修改不触碰原数组
arr.findIndex(v => compFn(v, val))遍历arr,找到第一个使compFn(v, val)为真值的元素下标;找不到时返回-1
if (i === -1) res.push(val)目标不存在,将新值追加到拷贝数组末尾
else res.splice(i, 1, val)目标存在,从下标i处删除 1 个元素并插入val,实现原位置替换
return res返回新数组,原数组保持不变

函数签名(arr, val, compFn)的三个参数含义如下:

  • arr:目标数组,函数不会修改它;
  • val:要替换或追加的新值(可以是对象、原始值等任意类型);
  • compFn:比较函数,接收数组中的现有元素与新值两个参数(compFn(v, val)),返回真值表示"这两个元素视为同一个"。

比较函数:让对象也能按业务键匹配

这是整个方案最灵活也最关键的部分。因为数组元素是对象时,indexOf/includes走的是严格引用比较(===),两个内容相同但引用不同的对象永远不会被判定为相等。而compFn允许我们自定义业务相等规则。

以原文档的示例(replace-or-append-array-value.md)为例,按name字段判断同一人:

const people = [ { name: 'John', age: 30 }, { name: 'Jane', age: 28 }, ]; const jane = { name: 'Jane', age: 29 }; // 名字相同 → 视为已存在,应替换 const jack = { name: 'Jack', age: 28 }; // 名字不同 → 不存在,应追加 // 按 name 匹配:Jane 已存在,原地替换为 age: 29 replaceOrAppend(people, jane, (a, b) => a.name === b.name); // [ { name: 'John', age: 30 }, { name: 'Jane', age: 29 } ] // Jack 不存在,追加到末尾 replaceOrAppend(people, jack, (a, b) => a.name === b.name); // [ // { name: 'John', age: 30 }, // { name: 'Jane', age: 28 }, // { name: 'Jack', age: 28 } // ]

注意第二个示例的结果:追加的是jack,而数组中原有的Jane条目未被触碰,这正是"仅在存在时替换"语义的体现。

compFn的常见写法还有:

// 按 id 匹配 replaceOrAppend(users, newUser, (a, b) => a.id === b.id); // 多个字段联合匹配 replaceOrAppend(items, newItem, (a, b) => a.code === b.code && a.type === b.type ); // 忽略大小写的匹配 replaceOrAppend(names, 'BOB', (a, b) => a.toLowerCase() === b.toLowerCase() );

不可变更新:为什么先做浅拷贝

原文档特别强调:与其原地修改数组,不如返回一个包含更新后元素的新数组。这里有几个关键考量:

  1. 避免副作用:调用方持有的原数组引用不被破坏,便于 React 等框架按引用比较判断状态变化、也便于函数式编程中保持数据不可变;
  2. 浅拷贝的边界:[...arr]只复制数组第一层元素引用,元素内部对象仍与源共享引用。本函数只做"整体替换某个元素"的操作,不修改元素内部属性,因此浅拷贝足够;若需要深拷贝元素,则要考虑structuredClone等深拷贝手段;
  3. 语义清晰:函数名replaceOrAppend表达的是"计算新数组"的纯函数式行为,返回值即结果,可安全用于链式调用或作为 reducer 的一部分。

该片段与 30 seconds of code 仓库的js/array集合定位一致——content/collections/js/array.yaml 将这类代码定义为"ES6 辅助函数"集合的一部分,覆盖数组、对象、算法等主题,强调短小、可复用、可直接复制进项目的工具函数风格。

边界情况与易错点

结合实现可以推导出以下几点值得注意:

  • findIndex只命中第一个匹配项:若数组中有多个元素满足compFn,只有第一个会被替换,其余保持不变;
  • compFn是必需的:省略或传空函数会导致永远找不到目标(或全部误判),所以调用方必须根据业务语义提供;
  • 原始值数组同样适用:例如按值相等的数组,可传入(a, b) => a === b;
  • 替换保持原位置:splice(i, 1, val)不会改变数组顺序,替换值占据原下标;追加则总是发生在末尾;
  • 原数组不被修改:任何一次调用后,arr的内容都与调用前一致,这便于在测试中验证纯函数行为。

仓库定位与使用建议

该片段属于 30 seconds of code 仓库的 JavaScript 数组主题内容,同目录下还有 array-includes-value.md、array-comparison.md、append-elements-to-array.md 等一批数组操作片段,可按需组合使用。将该函数直接复制进项目工具库即可,无需引入任何依赖:

// lib/array.js export const replaceOrAppend = (arr, val, compFn) => { const res = [...arr]; const i = arr.findIndex(v => compFn(v, val)); if (i === -1) res.push(val); else res.splice(i, 1, val); return res; };

一句话总结:findIndex负责定位、splice负责替换、push负责追加、扩展运算符负责隔离副作用,四个原语组合出一行核心逻辑,即可在任何需要"幂等写入数组"的场景中直接复用。

  • 教程
  • 文档

【免费下载链接】30-seconds-of-code

Coding articles to level up your development skills

项目地址:https://gitcode.com/gh_mirrors/30/30-seconds-of-code
点击查看免费下载

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

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

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

立即咨询