- 开发工具
【免费下载链接】collect.js
💎 Convenient and dependency free wrapper for working with arrays and objects
replace()是 collect.js 提供的集合覆盖方法,它在行为上与merge()相似,但更进一步:除了覆盖字符串键的匹配项之外,还会覆盖集合中数字键匹配的元素。本文将以 replace() 官方文档 为核心,结合 replace 源码、replace 测试用例 以及merge()、union()、replaceRecursive()等相邻方法,系统讲解replace()的语法、源码实现、各类输入场景的实战示例与注意事项,帮助你精准掌握这一数据覆盖工具的使用边界。
一、replace() 是什么:与 merge() 的核心区别
在 collect.js 中,replace()用于将传入的数组或对象"替换"进当前集合。根据 官方文档 的定义:
The replace method behaves similarly to merge; however, in addition to overwriting matching items with string keys, the replace method will also overwrite items in the collection that have matching numeric keys.
即:replace()的行为与merge()类似,但除了覆盖字符串键匹配的项之外,它还会覆盖集合中数字键匹配的项。这是它相对于merge()最关键的差异点——对**数字键(数组索引)**的处理策略不同。
从源码层面看,这一差异体现在数组集合的处理上。对比两个方法的实现:
- merge 源码:当集合和传入值都是数组时,
merge()直接执行this.items.concat(arrayOrObject),也就是将新值追加到数组末尾(append 语义)。 - replace 源码:当传入值是数组时,
replace()通过map按索引逐一比对,用items[index]覆盖同索引的旧值(overwrite 语义)。
因此,同样是向数组集合写入新内容,merge()会"接在后面",而replace()会"按位置顶替"。
二、replace() 的基础用法
replace()的调用方式与merge()一致,直接以数组、普通对象或另一个 Collection 作为参数调用即可。文档给出了最典型的使用示例——用一个对象替换集合对象中的字段:
const collection = collect({ name: 'Bob', }); const replaced = collection.replace({ name: 'John', number: 45, }); replaced.all(); // { // name: 'John', // number: 45, // }在这个例子中:
name是原集合中已存在的字符串键,replace()用'John'覆盖了'Bob';number是新引入的键,直接添加到结果集合中;replaced.all()返回底层数据,结果为{ name: 'John', number: 45 }。
关于all()方法,其实现见 all 源码,它只是原样返回this.items,是查看集合底层数据最常用的方法。
三、源码级实现剖析
replace()的完整实现位于 src/methods/replace.js,代码非常精简,共 21 行,其逻辑按参数类型分为三个分支:
'use strict'; module.exports = function replace(items) { if (!items) { return this; } if (Array.isArray(items)) { const replaced = this.items.map((value, index) => items[index] || value); return new this.constructor(replaced); } if (items.constructor.name === 'Collection') { const replaced = { ...this.items, ...items.all() }; return new this.constructor(replaced); } const replaced = { ...this.items, ...items }; return new this.constructor(replaced); };1. 空参数处理:原样返回
if (!items) { return this; }当传入值为null、undefined、0、''等 falsy 值时,replace()直接返回当前集合本身,不做任何替换。这一点在 replace 测试 中有明确验证:
it('doesnt replace anything when passing null', () => { const data = [1, 2, 3]; const collection = collect(data); expect(collection.replace(null).all()).to.eql([1, 2, 3]); expect(collection.all()).to.eql(data); });注意测试同时断言了collection.all()仍等于原始data,说明集合本身未被修改。
2. 数组输入:按索引覆盖
if (Array.isArray(items)) { const replaced = this.items.map((value, index) => items[index] || value); return new this.constructor(replaced); }当替换参数是数组时,replace()对this.items执行map:对于每个索引位置,若替换数组中存在对应索引的值(且该值非 falsy),则使用替换值;否则保留原值。也就是说,替换数组只覆盖它自身包含的索引,其余索引的原值保持不变,且替换结果中索引数量以原集合为准。
例如原集合['a', 'b', 'c']与替换数组[1, 2]:
const collection = collect(['a', 'b', 'c']); const replaced = collection.replace([1, 2]); replaced.all(); // [1, 2, 'c']索引0、1被1、2覆盖,索引2在替换数组中不存在,保留原值'c'。
3. Collection 输入:展开后合并
if (items.constructor.name === 'Collection') { const replaced = { ...this.items, ...items.all() }; return new this.constructor(replaced); }当参数是另一个 Collection 实例时,通过items.all()取出其底层数据,再与this.items做对象展开合并。all()的实现在 all 源码,展开合并遵循"右侧覆盖左侧"的规则。
4. 普通对象输入:展开合并
const replaced = { ...this.items, ...items }; return new this.constructor(replaced);这是兜底分支:参数为普通对象(或类对象)时,直接使用对象展开语法{ ...this.items, ...items }完成合并,items中的键覆盖this.items中的同名键,新键则直接加入。
值得注意的细节是:无论输入是 Collection 还是普通对象,最终都返回new this.constructor(...),即返回一个新的集合实例,而不是修改原集合,这与put()(put 源码)直接原地修改this.items的行为不同,体现了replace()的不可变性(immutability)设计。
四、不同输入类型的实战示例
replace 测试 覆盖了五种典型场景,全部可作为实际开发中的参考。
场景 1:数组集合 + 数字键对象
对数组集合传入一个以数字为键的对象,可以精准替换指定索引位的值:
const data = ['a', 'b', 'c']; const collection = collect(data); const replaced = collection.replace({ 1: 'd', 2: 'e', }); replaced.all(); // { // 0: 'a', // 1: 'd', // 2: 'e', // }这里对象键1、2分别命中数组的索引1、2,对应位置被覆盖为'd'、'e',索引0保持不变。这正是文档所述"同时覆盖数字键匹配项"的典型体现。
场景 2:数组集合 + 数组
const data = ['a', 'b', 'c']; const collection = collect(data); const replaced = collection.replace([1, 2]); replaced.all(); // [1, 2, 'c']替换数组只覆盖自身长度范围内的索引。
场景 3:对象集合 + 普通对象
const data = { name: 'Bob', }; const collection = collect(data); const replaced = collection.replace({ 0: 9, name: 'John', }); replaced.all(); // { // 0: 9, // name: 'John', // }name字符串键被覆盖,数字键0也作为新键被加入(此时集合底层从对象视角看同时包含数字键与字符串键)。
场景 4:对象集合 + Collection
const data = { name: 'Bob', }; const collection = collect(data); const replaced = collection.replace(collect({ 0: 9, name: 'John', })); replaced.all(); // { // 0: 9, // name: 'John', // }传入另一个collect()创建的集合,效果与传入普通对象一致。
场景 5:传入 falsy 值
const data = [1, 2, 3]; const collection = collect(data); collection.replace(null).all(); // [1, 2, 3]不产生任何替换,集合保持不变。
此外,所有场景的测试都同时断言了collection.all()仍等于原始数据,再次确认replace()不会污染原集合。
五、replace() 与 merge()、union() 的对比
在 collect.js 中,replace()、merge()、union()三者都是"向集合写入外部数据"的方法,但语义截然不同:
| 方法 | 数组集合行为 | 对象集合行为 | 源码位置 |
|---|---|---|---|
replace() | 按索引覆盖已有元素 | 传入键覆盖原键(含数字键),新键追加 | src/methods/replace.js |
merge() | 追加到数组末尾(concat) | 传入键覆盖原键,新键追加 | src/methods/merge.js |
union() | 以对象语义处理,原集合已有键优先,只补充缺失键 | 原集合值优先,绝不覆盖已有键 | src/methods/union.js |
用一个例子直观对比(原集合{ a: 'A', b: 'B' },传入{ a: 'AAA', c: 'CCC' }):
replace()→{ a: 'AAA', c: 'CCC' }(完全覆盖与新增)merge()→{ a: 'AAA', b: 'B', c: 'CCC' }(覆盖 + 保留未涉及的键)union()→{ a: 'A', b: 'B', c: 'CCC' }(原值优先,仅补缺失键,见 union 文档)
对数组集合['a', 'b', 'c']传入[1, 2]:
replace()→[1, 2, 'c'](按索引覆盖)merge()→['a', 'b', 'c', 1, 2](追加到末尾,见 merge 文档)
选用建议:需要"原位顶替"数组元素时用replace();需要拼接追加时用merge();需要"保留原值、只补充缺失键"时用union()。
六、深层覆盖:replaceRecursive()
如果数据是嵌套结构,仅靠replace()只能覆盖顶层键,嵌套对象内部的同名键不会被递归处理。此时应使用replaceRecursive(),其行为与replace()一致,但会递归进入嵌套数组/对象并逐层执行同样的替换过程,实现见 replaceRecursive 源码。
官方 replaceRecursive 文档 给出的示例:
const collection = collect([ 'Matip', 'van Dijk', [ 'Núñez', 'Firmino', 'Salah', ], ]); const replaced = collection.replaceRecursive({ 0: 'Gomez', 2: { 1: 'Origi' }, }); replaced.all(); // { // 0: 'Gomez', // 1: 'van Dijk', // 2: { 0: 'Núñez', 1: 'Origi', 2: 'Salah' }, // }顶层索引0被替换为'Gomez',索引2处的嵌套数组内部,索引1被替换为'Origi',而嵌套数组中的其他元素(0、2位置)得以保留。这正是replace()与replaceRecursive()的分工:浅层覆盖用replace(),深层递归覆盖用replaceRecursive()。
七、注意事项与使用边界
结合源码与测试,使用replace()时有几点需要特别留意:
- 不可变性:
replace()总是返回新的集合实例(new this.constructor(...)),原集合不会被动修改。若需要保留原集合用于后续逻辑,可以直接复用原变量。 - falsy 参数直接短路:传入
null、undefined等 falsy 值时方法直接返回this,不做任何处理,调用方无需额外判空。 - 数组场景的"按索引覆盖"细节:替换数组只覆盖其自身存在的索引;且
map中使用了items[index] || value,若替换值为0、''、false等 falsy 值,该索引位置会保留原值而非被覆盖——这是源码实现的一个隐含行为,在需要覆盖为 falsy 值时应改用对象形式(如{ 1: 0 })。 - 严格比较语义:collect.js 所有方法默认使用严格相等(
===)进行比较,相关说明见 API 总览文档 中的 "Strictness and comparisons" 一节。 - TypeScript 类型:
replace()通过 index.d.ts 提供类型声明,可与 TypeScript 项目无缝集成;引入方式参见 usage 文档(支持require、import以及底层Collection类三种方式)。
八、小结
replace()是 collect.js 中"按键覆盖"语义最彻底的方法:对字符串键与数字键一视同仁,数组场景下按索引原位顶替,对象场景下直接展开合并,且始终保持不可变返回。理解它与merge()(追加/保留)、union()(原值优先)的差异,再结合replaceRecursive()处理嵌套结构,即可在数据清洗、配置合并、状态更新等场景中精准选择最合适的集合覆盖工具。
- 开发工具
【免费下载链接】collect.js
💎 Convenient and dependency free wrapper for working with arrays and objects
相关推荐
CUA字符串工具:扩展字符串功能的实用方法集合
CUA字符串工具:扩展字符串功能的实用方法集合 在AI代理开发中,字符串处理是基础但至关重要的环节。CUA(Computer Use Agents)框架提供了一
人工智能AI Agent大模型GUI 自动化MCP 服务模型评测微调强化学习工具调用SeaTunnel Replace Transform 详解:字符串字段子串与正则替换实战指南
SeaTunnel Replace Transform 详解:字符串字段子串与正则替换实战指南 导读 Replace 是 SeaTunnel Transform
数据工程大数据批处理流处理拆解SpotiFlyer架构:如何用Kotlin Multiplatform一套代码跑遍Android、桌面端和浏览器
拆解SpotiFlyer架构:如何用Kotlin Multiplatform一套代码跑遍Android、桌面端和浏览器 SpotiFlyer 是一款基于 Kot
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考