- 教程
- 文档
【免费下载链接】30-seconds-of-code
Coding articles to level up your development skills
导读
在 JavaScript 日常开发中,数组元素的删除是出现频率极高的操作,而"从数组的开头或结尾移除若干个元素"更是其中最常见、也最容易踩坑的场景。本文以 30 seconds of code 仓库中的 remove-first-last-n-array-elements.md 为骨架,系统讲解基于Array.prototype.slice()、splice()、shift()、pop()、findIndex()与findLastIndex()的六种删减方案,并深入剖析各 API 的边界行为与不可变编程(immutability)取舍。读完本文,你将能够针对"移除前/后 n 个元素"以及"按条件移除头部/尾部元素"两类需求,直接写出健壮、可复用的一行式工具函数。
从"删除单个元素"说起:shift()与pop()
在讨论批量移除之前,先回顾最基础的两个可变方法(mutative methods):
Array.prototype.shift():移除数组第一个元素,返回被移除的元素,原地修改数组。Array.prototype.pop():移除数组最后一个元素,返回被移除的元素,原地修改数组。
两者共同特点是:会改变原数组,且返回值是被删除的元素(而非删除后的数组)。它们只能一次删除一个元素。
const arr = ['a', 'b', 'c']; const first = arr.shift(); // 'a' const last = arr.pop(); // 'c' console.log(arr); // ['b']可以看到,arr在执行完shift()和pop()后只剩下['b']。这种副作用在某些场景(例如状态管理、纯函数编程)中是不可接受的,因此文章核心思路是:优先使用不修改原数组的Array.prototype.slice()。
批量移除的核心工具:splice()与slice()
当需要移除多个元素时,有两个主力方法:
| 方法 | 是否修改原数组 | 返回值 | 适用场景 |
|---|---|---|---|
Array.prototype.splice() | 修改(变异) | 被删除的元素组成的数组 | 明确需要原地删除,且关心被删元素 |
Array.prototype.slice() | 不修改 | 新的数组(原数组浅拷贝的子集) | 希望保持原数组不变,获得新数组 |
Array.prototype.splice(start, deleteCount)是删除元素的"通用工具",但它会变异原数组;而Array.prototype.slice(begin, end)则基于原数组返回一个全新的数组,原数组保持不变。
注:本文重点讲解
slice()路线,因为"变异原数组通常不是我们想要的结果"(原文语)。如需不修改原数组的splice()替代实现,仓库中有专门文章 remove-elements-from-array,其核心思路是用slice()+concat()组合出一个shank()函数。
从头部移除 n 个元素:slice(n)
使用slice()时,传入正数起始索引、不传结束索引即可:返回的新数组从第n个元素(索引n)开始,天然跳过前n个元素。
const drop = (arr, n = 1) => arr.slice(n); drop([1, 2, 3]); // [2, 3] drop([1, 2, 3], 2); // [3] drop([1, 2, 3], 42); // []注意两个细节:
- 默认参数
n = 1让"只移一个"成为默认行为; - 当
n超过数组长度时(如42),slice()会安全地返回空数组[],不会抛错。
从尾部移除 n 个元素:slice(0, -n)
从尾部移除则需要负数的结束索引:slice(0, -n)表示"取从索引 0 开始、到倒数第n个元素为止(不含)"的新数组,从而把最后n个元素剔除。
const dropLast = (arr, n = 1) => arr.slice(0, -n); dropLast([1, 2, 3]); // [1, 2] dropLast([1, 2, 3], 2); // [1] dropLast([1, 2, 3], 42); // []同样地,n过大时slice()返回[],行为是安全的。
边界提醒:
slice()对负数与越界索引有非常宽容的处理逻辑。传入0作为n(如slice(0, 0))会得到空数组,而传入负的n则会产生与直觉相反的结果。仓库姊妹文章 first-last-n-elements 与 take-n-elements-from-array-start-or-end 对此有更深入的讨论,必要时可对n做n >= 0校验或使用三元表达式兜底。
按条件移除:从"删除固定数量"到"删除直到条件成立"
Array.prototype.filter()天然适合"删除所有满足条件的元素",但它必须遍历整个数组;当我们需要"从头部开始,删除所有元素直到遇到第一个满足条件的元素为止"(即"删除前缀")时,filter()就显得笨重且低效。
更优的思路是两步走:
- 用
findIndex()/findLastIndex()定位第一个/最后一个满足条件的元素; - 用
slice()切出需要的部分。
按条件移除头部元素:findIndex()+slice()
const dropUntil = (arr, func) => { const index = arr.findIndex(func); return arr.slice(index >= 0 ? index : arr.length); } dropUntil([1, 2, 3, 4], n => n >= 3); // [3, 4]执行流程分析:
findIndex(func)返回数组中第一个使func为真的元素索引;- 如果找到(
index >= 0),从该索引开始切片,前缀被丢弃; - 如果没有找到(
findIndex返回-1),则回退到arr.length——slice(arr.length)得到空数组[],语义为"所有元素都被丢弃"。
对[1, 2, 3, 4]与条件n => n >= 3,第一个满足条件的元素是3(索引 2),于是返回[3, 4]。
按条件移除尾部元素:findLastIndex()+slice()
对称地,移除尾部满足条件的元素需要定位最后一个满足条件的元素。Array.prototype.findLastIndex()是从数组末尾向前搜索的现代方法(与findIndex()互补):
const dropLastUntil = (arr, func) => { const index = arr.findLastIndex(func); return arr.slice(0, index >= 0 ? index + 1 : arr.length); } dropLastUntil([1, 2, 3, 4], n => n < 3); // [1, 2]这里的关键差异在于切片结束索引的计算:
- 找到最后一个满足条件的元素索引
index后,需要保留包含该元素在内的前缀,因此结束索引取index + 1; - 若找不到(
-1),则回退为arr.length,返回整个数组(语义为"一个都不删")。
对[1, 2, 3, 4]与条件n => n < 3,最后一个小于 3 的元素是2(索引 1),slice(0, 2)得到[1, 2]。
六种方案速查表与取舍建议
| 需求 | 工具函数 | 是否变异原数组 | 备注 |
|---|---|---|---|
| 移除头部 1 个 | arr.shift() | 是 | 返回被删元素 |
| 移除尾部 1 个 | arr.pop() | 是 | 返回被删元素 |
| 移除头部 n 个 | drop(arr, n) => arr.slice(n) | 否 | n越界安全返回[] |
| 移除尾部 n 个 | dropLast(arr, n) => arr.slice(0, -n) | 否 | n越界安全返回[] |
| 按条件移除头部 | dropUntil(arr, fn)用findIndex+slice | 否 | 找不到时返回[] |
| 按条件移除尾部 | dropLastUntil(arr, fn)用findLastIndex+slice | 否 | 找不到时返回原数组 |
选择建议:
- 追求性能且不关心原数组→
shift()/pop()/splice(); - 追求不可变性(immutability)→
slice()系列,这也是原文重点强调的默认选择; - 需要按条件批量删除 →
findIndex()/findLastIndex()定位 +slice()切片; - 需要删除任意位置/全部匹配的元素 → 参考仓库中的 remove-element-from-array、remove-elements-from-array 与 remove-matching-array-elements 三篇文章。
从源码看这些技巧在项目中的位置
本文的六个工具函数(drop、dropLast、dropUntil、dropLastUntil)在 30 seconds of code 中属于JavaScript 数组(array)主题的经典技巧,与以下仓库资源同属一个知识簇:
- first-last-n-elements:取前/后 n 个元素(
slice(0, n)/slice(-n)),与本文的"删除"互为镜像; - take-n-elements-from-array-start-or-end:深入讨论了
n = 0、n越界、负值等slice()边界行为; - take-elements-by-condition-from-array-start-or-end:按条件保留前缀/后缀的
takeWhile/takeRightWhile,与本文dropUntil/dropLastUntil形成"保/删"对照; - array-head-tail:数组
head/tail的基础概念,同样基于slice(1)实现; - fast-remove-array-element:对无序大数组用"拷贝末位 +
pop()"的提速技巧。
这些文章统一使用Array.prototype.slice()作为核心原语,体现了 30 seconds of code 一以贯之的"短小、无依赖、可直接复用"的风格;本文的函数体均可在项目中以单行/数行的形式直接落地。
结语
处理数组头尾元素的移除,核心是两对选择:可变 vs 不可变(shift()/pop()/splice()vsslice()),以及固定数量 vs 按条件(slice(n)vsfindIndex/findLastIndex+slice)。掌握slice()对负数索引与越界索引的宽容语义,再配合findIndex()/findLastIndex()的定位能力,你就能用几行代码优雅地覆盖从"删一个"到"删到条件成立"的全部场景,同时保持原数组不变、避免不必要的性能损耗。
- 教程
- 文档
【免费下载链接】30-seconds-of-code
Coding articles to level up your development skills
相关推荐
如何用蚁群算法5步找出城市最短巡回路线:scikit-opt 实战
如何用蚁群算法5步找出城市最短巡回路线:scikit opt 实战 给定一组城市的坐标,如何快速找出一条经过所有城市再回到起点的环线?这是经典的旅行商问题(TS
教程文档在 JavaScript 中获取数组头部(head)与尾部(tail)元素:30-seconds-of-code 实战指南
在 JavaScript 中获取数组头部(head)与尾部(tail)元素:30 seconds of code 实战指南 数组的 头部(head) 指其第一个
教程文档30 Seconds of Code:用 slice 与 filter 取 JavaScript 数组的第 n 个元素
30 Seconds of Code:用 slice 与 filter 取 JavaScript 数组的第 n 个元素 数组按索引取值是 JavaScript
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考