- 教程
- 文档
【免费下载链接】30-seconds-of-code
Coding articles to level up your development skills
在处理对象数组时,经常需要"若已存在则替换、不存在则追加"的幂等更新逻辑。本文基于 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 给出了一种简洁、通用的实现,本文将其展开并深入拆解。
核心思路:三步走的算法设计
原文档给出的算法可以归纳为三步:
- 判定存在性:使用
Array.prototype.findIndex()配合一个比较函数compFn找到目标项在数组中的下标; - 分支处理:若下标为
-1(不存在),用Array.prototype.push()追加到末尾;否则用Array.prototype.splice()在原下标处替换; - 返回新数组:函数开头先用扩展运算符
...创建原数组的浅拷贝,所有修改都作用在拷贝上,最后返回新数组,保证原数组不被污染。
完整实现与逐行解析
以下是原文档中的完整实现(位于 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() );不可变更新:为什么先做浅拷贝
原文档特别强调:与其原地修改数组,不如返回一个包含更新后元素的新数组。这里有几个关键考量:
- 避免副作用:调用方持有的原数组引用不被破坏,便于 React 等框架按引用比较判断状态变化、也便于函数式编程中保持数据不可变;
- 浅拷贝的边界:
[...arr]只复制数组第一层元素引用,元素内部对象仍与源共享引用。本函数只做"整体替换某个元素"的操作,不修改元素内部属性,因此浅拷贝足够;若需要深拷贝元素,则要考虑structuredClone等深拷贝手段; - 语义清晰:函数名
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
相关推荐
30 Seconds of Code 数组分组实战:用 reduce 按函数或属性名实现 groupBy
30 Seconds of Code 数组分组实战:用 reduce 按函数或属性名实现 groupBy 导读 在 JavaScript 开发中,把数组元素按照
教程文档30 Seconds of Code 技巧:用 `isSorted` 高效判断 JavaScript 数值数组的升序与降序
30 Seconds of Code 技巧:用 isSorted 高效判断 JavaScript 数值数组的升序与降序 在 JavaScript 开发中,判断一
教程文档30 seconds of code 实战:用 JavaScript 将 `hsl()` 颜色字符串转换为数组或对象
30 seconds of code 实战:用 JavaScript 将 hsl 颜色字符串转换为数组或对象 本篇技术指南源自仓库 content/snippe
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考