JavaScript Array.splice() 深度实战指南:从基础用法到 refine 项目源码中的真实应用 JavaScript Array.splice() 深度实战指南从基础用法到 refine 项目源码中的真实应用【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refineArray.prototype.splice()是 JavaScript 中用于原地in-place删除、替换或插入数组元素的核心方法也是 refine 这类以数据操作为核心的 React 框架中高频使用的底层工具。本篇以 refine 仓库官方博客教程为主体结合仓库真实源码中的 6 处调用场景从方法签名、参数语义、边界情况到多维数组与解构组合系统讲解splice()的完整用法读完你既能熟练驾驭该方法也能理解它在真实项目中的落地模式。JavaScript Splice方法签名与返回语义当数组调用splice()时它接受三类可选参数startIndex起始索引、deleteCount删除数量以及可变的items 插入列表。其中startIndex是从零开始的索引表示变更动作从哪里开始deleteCount表示要删除的条目数量items 则是可能无限多个、需要插入的条目。JSArray.prototype.splice()方法签名Array.prototype.splice()的合法调用签名如下splice() splice(startIndex) splice(startIndex, deleteCount, item1) splice(startIndex, deleteCount, item1, item2, /*...*/, itemN)Array.prototype.splice()的所有参数都是可选的。不过如果我们希望调用数组真正发生变更至少要传入startIndex。是否传deleteCount取决于我们想不想删除条目是否传items 列表取决于我们想不想插入或追加条目。三个参数也可以同时传入后续章节会逐一覆盖这些情况。JSsplice()的返回值splice()会变更原始数组并返回一个包含被删除条目的数组。这是它与slice()最本质的区别之一它不返回新数组的副本而是切除下来的部分。无参数调用splice()虽然没有参数时splice()不会对调用数组做任何修改但它能帮助我们直观理解返回值语义——返回的是被删除条目的数组const mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; // 返回值 console.log(mnemonic.splice()); // [] console.log(mnemonic); // [Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked]可以看到无参数调用返回空数组[]。原因在于没有传入startIndex没有任何条目被移除因此被删除条目数组自然是空的。这也印证了一点虽然splice()天生用于移除条目但没有startIndex时它不会改变调用数组。只传startIndex清空第n项之后的所有元素当只传入startIndex时从startIndex含开始到数组末尾的所有条目都会被移除const mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; // 返回值 console.log(mnemonic.splice(4)); // [And, Zebras, In, Large, Cages, Make, Sure, Padlocked] // 调用数组 console.log(mnemonic); // [Please, Send, Cats, Monkeys]这里的关键在于移除从startIndex处含开始。因此零基的startIndex恰好等于调用数组中保留的前n个条目数量即n startIndexconst mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; // 只保留前 4 项 console.log(mnemonic.splice(4)); // [And, Zebras, In, Large, Cages, Make, Sure, Padlocked] console.log(mnemonic); // [Please, Send, Cats, Monkeys] // 只保留前 2 项 console.log(mnemonic.splice(2)); // [Cats, Monkeys] console.log(mnemonic); // [Please, Send] // 清空整个数组 console.log(mnemonic.splice(0)); // [Please, Send] console.log(mnemonic); // []在上面的例子中传入的每个startIndex值都代表着我们希望在原数组头部保留的条目数量。mnemonic.splice(0)则是彻底清空原数组的快捷方式。仓库实战这种只传startIndex截断尾部的写法在 packages/strapi/src/hooks/upload.ts 中有直接应用。Strapi 数据提供者的beforeUpload会统计待上传文件与已上传文件的总数当超过maxCount上限时用files.splice(deleteItemCount)一步把超出配额的多余文件从待上传列表中切除const beforeUpload (_file: any, files: any[]): boolean { const totalFiles fileList.length; const filesCount files.length; if (totalFiles filesCount maxCount) { const excessFileCount totalFiles filesCount - maxCount; // 转成负数从尾部截断 const deleteItemCount excessFileCount - excessFileCount * 2; files.splice(deleteItemCount); } setFileList([...fileList, ...files]); return true; };这正是从第 n 项之后全部移除语义的典型工程化用法用一个负数超过数组长度的负值实际等效于0详见下文边界情况或正索引快速裁剪数组尾部。传入deleteCount删除中间条目通过第二个参数deleteCount我们可以删除从startIndex开始、指定数量的中间条目const mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; console.log(mnemonic.splice(4, 2)); // [And, Zebras] console.log(mnemonic); // [Please, Send, Cats, Monkeys, In, Large, Cages, Make, Sure, Padlocked]上面的调用传入了2作为deleteCount。删除从startIndex开始直到删满deleteCount个为止。注意删除后deleteCount之后的条目会向左侧滑动补齐空位。仓库实战按索引删除单个元素是splice(index, 1)最常见的形态。Mantine 数据提供者的通知管理实现中packages/mantine/src/providers/notificationProvider.tsx 的removeNotification先用indexOf找到通知 key 的位置再删除该元素const removeNotification (key?: string) { if (key) { const index activeNotifications.indexOf(key); if (index -1) { activeNotifications.splice(index, 1); } } };同样refine 的 codemod 在把 v1 代码迁移到 v2 时需要从Refine组件的 JSX 属性数组中剔除多余的routes属性packages/codemod/src/transformations/refine1-to-refine2.ts 正是用openingElement.attributes?.splice(routesAttributeIndex, 1)完成的。由于splice()是原地修改配合indexOf/findIndex定位索引即可在真实数据数组上实现精准删除。替换条目删除与插入的优先级在第二个参数之后传入条目列表就可以用新条目替换被删除的条目。所有传入的条目都会被添加到startIndex及之后的位置const mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; console.log(mnemonic.splice(4, 0, Ants, Zebus)); // [] console.log(mnemonic); // [Please, Send, Cats, Monkeys, Ants, Zebus, And, Zebras, In, Large, Cages, Make, Sure, Padlocked] console.log(mnemonic.splice(4, 2)); // [Ants, Zebus] console.log(mnemonic); // [Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked] // 先删除、再替换 console.log(mnemonic.splice(4, 2, Alpacas, Zorillas)); console.log(mnemonic); // [Please, Send, Cats, Monkeys, Alpacas, Zorillas, In, Large, Cages, Make, Sure, Padlocked]观察上面的连续变更可以看出删除优先于插入。当deleteCount为正数且后续传入替换条目时引擎先删除指定数量的条目再把新条目放进startIndex及之后。在上面的序列中Ants, Zebus先以0删除量插入到索引4随后以startIndex 4, deleteCount 2且不带替换条目被删除最后一次变更又在索引4删除了 2 个条目并用新条目替换。我们还可以让插入的条目多于删除的条目从而撑大overload数组const mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; // 插入的条目数 删除的条目数 console.log(mnemonic.splice(4, 2, Ants, Zebus, Donkeys, Bears)); // [And, Zebras] console.log(mnemonic); // [Please, Send, Cats, Monkeys, Ants, Zebus, Donkeys, Bears, In, Large, Cages, Make, Sure, Padlocked]注意因为替换/扩容发生在中间位置startIndex之后的原有条目会被向右推挤。仓库实战这是splice()的替换片段用法。refine 的查询 key 构建器packages/core/src/definitions/helpers/keys/index.ts在arrayReplace中先浅拷贝数组再借助arrayFindIndex定位需要替换的子数组起始位置然后一次调用完成删除旧片段 展开插入新片段export function arrayReplaceT( array: T[], partToBeReplaced: T[], newPart: T[], ): T[] { const newArray: T[] [...array]; const startIndex arrayFindIndex(array, partToBeReplaced); if (startIndex ! -1) { newArray.splice(startIndex, partToBeReplaced.length, ...newPart); } return newArray; }newArray.splice(startIndex, partToBeReplaced.length, ...newPart)是先删后插的标准写法deleteCount等于被替换片段的长度配合展开运算符把新片段逐项插入——这正是 replace 语义在框架底层如动态更新查询 key 分段的典型实现。注意这里对副本调用splice()避免了对外部传入数组的原地污染属于防御性编程的最佳实践。纯注入deleteCount传0如果只想在某个位置注入条目而不删除任何元素把deleteCount设为0即可const mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; console.log(mnemonic.splice(4, 0, Ants, Zebus, Donkeys, Bears)); // [] console.log(mnemonic); // [Please, Send, Cats, Monkeys, Ants, Zebus, Donkeys, Bears, And, Zebras, In, Large, Cages, Make, Sure, Padlocked]由于deleteCount为0没有任何条目被删除新条目直接从startIndex位置开始插入。仓库实战codemod 在 v5 迁移脚本中需要把新声明的变量语句插到指定语句之后packages/codemod/src/transformations/v5/use-query-and-result-fields-in-list-hooks.ts 使用的正是statements.splice(currentIndex 1, 0, newDeclaration)—— 以0删除量、目标位置为起点完成纯注入。尾部追加用length作为startIndex使用调用数组的length作为startIndex可以往数组尾部追加条目。因为length大于零基数组的最后一个索引所以它天然指向末尾之后的位置const mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; console.log( mnemonic.splice(mnemonic.length, 0, Ants, Zebus, Donkeys, Bears), ); // [] console.log(mnemonic); // [Please, Send, Cats, Monkeys, In, Large, Cages, Make, Sure, Padlocked, Ants, Zebus, Donkeys, Bears]像这样结合length使用splice()是Array.prototype.push()的一个不错的替代方案——尤其当你希望保持统一用splice()处理插入的代码风格时。⚠️重要警告用-1作为startIndex不能实现尾部追加。因为当startIndex 0时变更从startIndex mnemonic.length开始即-1 12 11也就是最后一个元素的位置。于是最后一个元素总会被挤到末尾永远无法真正追加到尾部const mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; console.log(mnemonic.splice(-1, 0, Ants, Zebus, Donkeys, Bears)); // [] console.log(mnemonic); // [Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Ants, Zebus, Donkeys, Bears, Padlocked]负数startIndex在最后n项之前注入将-n作为startIndex可以在最后n个条目之前注入新条目这是startIndex 0的又一典型场景const mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; // 在最后 2 项之前注入 console.log(mnemonic.splice(-2, 0, Ants, Zebus, Donkeys, Bears)); // [] console.log(mnemonic); // [Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Ants, Zebus, Donkeys, Bears, Sure, Padlocked] // 在最后 4 项之前注入 console.log(mnemonic.splice(-4, 0, Apacas, Zorillas)); // [] console.log(mnemonic); // [Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Ants, Zebus, Apacas, Zorillas, Donkeys, Bears, Sure, Padlocked]负数startIndex会换算为-n mnemonic.length因此变更从距数组末尾n个条目注意是条数不是索引处开始从而保证末尾n个条目保持不动。仓库实战把负数索引注入与删除 头部插入组合可以实现把某个元素移动到数组首位。kbar 命令面板的 packages/kbar/src/hooks/useRefineKbar/index.tsx 中moveActionToFirst先复制资源数组findIndex定位当前路由对应的资源然后两次splice完成移动const moveActionToFirst (): IResourceItem[] { const orderedResources [...resources]; const fromIndex orderedResources?.findIndex( (resource) (resource.identifier ?? resource?.name) (resourceFromParams?.identifier ?? resourceFromParams?.name), ); if (fromIndex 0) { const element orderedResources[fromIndex]; orderedResources.splice(fromIndex, 1); orderedResources.splice(0, 0, element); } return orderedResources; };splice(fromIndex, 1)取出目标元素splice(0, 0, element)再把它注入到头部——两个操作都基于原地修改 删除优先于插入的语义拼装出移动到首位的效果同时保留其余资源的相对顺序。边界情况极端的startIndex值过高负值startIndex -length时等效于0当startIndex小于-mnemonic.length时它会被钳制为0const mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; console.log(mnemonic.splice(-100, 10, Ants, Zebus, Donkeys, Bears)); // [Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make] console.log(mnemonic); // [Ants, Zebus, Donkeys, Bears, Sure, Padlocked]这里startIndex为-100但数组长度只有 12-100 12 -88仍为负数最终按0处理。因此删除从索引0开始、共移除前 10 个条目传入的条目在删除后从索引0处插入。这正是上文 Strapi 上传示例中deleteItemCount取负值的数学基础只要负得足够大splice(负数)就等价于splice(0)即从头开始切除。过高正值startIndex length时退化为尾部追加当startIndex大于数组长度时即使传入了deleteCount也不会删除任何条目所有传入条目会被追加到尾部const mnemonic [ Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, ]; console.log(mnemonic.splice(100, 10, Ants, Zebus, Donkeys, Bears)); // [] console.log(mnemonic); // [Please, Send, Cats, Monkeys, And, Zebras, In, Large, Cages, Make, Sure, Padlocked, Ants, Zebus, Donkeys, Bears]如我们所见100远大于mnemonic.length而且我们要求删除 10 个并不存在的条目所以数组内部没有任何元素被删除传入条目被追加到了尾部。需要提醒的是用任意过大的索引做尾部追加并不安全因为它无法保证startIndex一定大于等于调用数组的length例如传入的数组可能比预期更长。更稳妥的做法是显式传arr.length。Bonussplice()vsslice()splice()和slice()经常被初学者混淆原因很简单——它们的名字太像了。但二者的行为截然不同splice()会修改原始数组。可用于在指定索引处删除、替换或插入元素。原地修改数组并返回被删除元素的数组。示例let arr [1, 2, 3, 4]; let removed arr.splice(2, 2); // 从索引 2 开始删除 2 个元素 console.log(arr); // [1, 2] - 原始数组被修改 console.log(removed); // [3, 4] - 被删除的元素slice()不会修改原始数组。返回从起始索引到结束索引之间一部分数组的浅拷贝。原始数组保持不变。示例let arr [1, 2, 3, 4]; let sliced arr.slice(2, 4); // 复制索引 2 到 4不含 4之间的元素 console.log(arr); // [1, 2, 3, 4] - 原始数组未被修改 console.log(sliced); // [3, 4] - 切片部分关键区别splice()会修改原始数组slice()不会。需要变更数组删除、插入、替换时用splice()需要读取数组的一部分而不改变原数组时用slice()。多维数组中的splice()当数组的元素本身还是数组多维数组时splice()既可以作用于外层数组增删整个内层数组也可以先通过索引访问到内层数组再对其调用splice()。修改外层数组用splice()从外层数组中整体增删内层数组let matrix [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ];弹出第二行即数组[4, 5, 6]let removedRow matrix.splice(1, 1); // 删除索引 1 处的 1 个元素 console.log(matrix); // 输出: [[1, 2, 3], [7, 8, 9]] console.log(removedRow); // 输出: [[4, 5, 6]]修改内层数组也可以对内层数组的元素执行splice()// 删除第一行的第二个元素 let removedElement matrix[0].splice(1, 1); // 删除第一个内层数组中索引 1 处的 1 个元素 console.log(matrix); // 输出: [[1, 3], [7, 8, 9]] console.log(removedElement); // 输出: [2]向内层数组插入元素同理可以向内层数组注入元素// 填充第二行 matrix[1].splice(2, 0, 10, 11); // 在第二个内层数组的索引 2 处插入 10 和 11 console.log(matrix); // 输出: [[1, 3], [7, 8, 10, 11, 9]]应用示例日程表假设我们用多维数组表示一周日程每个内层数组代表当天的任务let weeklySchedule [ [Meeting, Coding, Review], // 周一 [Planning, Design], // 周二 [Deployment, Testing], // 周三 ];如果要把周二日程中的 Design 划掉weeklySchedule[1].splice(1, 1); // 删除周二数组索引 1 处的 1 个元素 console.log(weeklySchedule); // 输出: [[Meeting, Coding, Review], [Planning], [Deployment, Testing]]要点总结访问内层数组先通过外层数组索引拿到内层数组再对它调用splice()。替换内层元素splice()可以增、删、改内层数组中的元素。修改外层结构splice()可以整体增删外层数组中的内层数组。结合解构使用splice()Array.prototype.splice()返回被删除元素数组这一特性配合解构赋值可以写出非常简洁的代码——一次调用同时完成移除 变量绑定。基础用法解构被移除的元素let arr [1, 2, 3, 4, 5]; // 移除前两个元素并解构返回值 let [first, second] arr.splice(0, 2); console.log(first); // 输出: 1 console.log(second); // 输出: 2 console.log(arr); // 输出: [3, 4, 5] - 原始数组已被修改元素交换splice()和解构也可以组合出更实际的用法例如交换数组元素let numbers [10, 20, 30, 40, 50]; // 交换第 2 个和第 4 个元素 [numbers[1], numbers[3]] [numbers[3], numbers[1]]; console.log(numbers); // 输出: [10, 40, 30, 20, 50]批量提取多个元素可以用splice()一次提取数组的多段内容并立即解构使用let fruits [apple, banana, cherry, date, elderberry]; // 从索引 1 开始删除 3 个元素 const [firstFruit, secondFruit, thirdFruit] fruits.splice(1, 3); console.log(firstFruit); // 输出: banana console.log(secondFruit); // 输出: cherry console.log(thirdFruit); // 输出: date console.log(fruits); // 输出: [apple, elderberry]删除 插入一步完成deleteCount为 0 时返回值是空数组而配合正数deleteCount则可以在删除并替换的同时把被删除项解构出来let arr [1, 2, 3, 4, 5]; // 从索引 2 删除 2 个元素并替换为 a、b同时解构被删除元素 let [removed1, removed2] arr.splice(2, 2, a, b); console.log(removed1); // 输出: 3 console.log(removed2); // 输出: 4 console.log(arr); // 输出: [1, 2, a, b, 5] - 数组已被新元素替换这个例子中元素3和4被删除a和b插入到它们的位置同时被删除的元素被解构到变量removed1和removed2中。为什么推荐这种写法代码更简洁配合解构可以少写很多先删、再取引用的样板代码。直接访问被移除的元素立即可用于后续逻辑数组在同一语句中就完成了变更逻辑连贯、可读性强。总结本篇围绕Array.prototype.splice()展开了系统梳理它是数组方法没有对应的 String 版本它会原地修改原数组并返回被删除元素的数组。我们掌握了以下模式只传n作为startIndex可以清空前n项之后的所有条目splice(0)即清空整个数组传deleteCount作为第二参数可以删除指定数量的中间条目在第三参数及之后传入新条目可以实现替换甚至可以撑大数组deleteCount传0则实现纯注入用调用数组的length作为startIndex可以尾部追加作为push()的替代用-n作为startIndex可以在最后n项之前注入极端值方面startIndex -length时等效为0startIndex length时不会删除任何条目、新条目直接进入尾部。在 refine 仓库中splice()的这些语义被真实地应用于框架与工具链core的查询 key 构建器用它做数组片段替换keys/index.tskbar命令面板用它实现资源移动到首位useRefineKbar/index.tsxMantine 通知提供者用它按索引关闭通知notificationProvider.tsxStrapi 上传钩子用它裁剪超配额文件upload.tscodemod 迁移脚本则用它删除 JSX 属性与注入新语句refine1-to-refine2.ts、use-query-and-result-fields-in-list-hooks.ts。掌握了这些真实调用形态你就能在自己的项目中更自信地选择splice()处理数组变更。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考