JS数组删除第一个和最后一个元素的方法
详细讲解JavaScript中删除数组第一个和最后一个元素的方法。涵盖pop、shift、splice、slice等核心方法的操作步骤、原地修改与不可变更新的区别、底层运行机制及场景选用建议。
pop(),删除第一个元素则是 shift();若需在不改变原数组的前提下获得剔除首尾元素的新数组,则可以使用 slice() 或现代不可变方法 toSpliced()。本文将系统梳理这两种场景下的具体操作方法、运行机制及选用考量。
一、删除最后一个元素(尾部删除)
删除数组末尾的元素在日常开发中十分普遍,例如撤销历史操作、栈结构的出栈等。根据是否允许修改原数组,有两种主流处理方式。
1. 原地修改:使用 array.pop()
pop() 是 JavaScript 数组内置的破坏性方法。它会直接移除数组的最后一项,减少数组的 length,并返回被移除的那项元素。若在空数组上执行,则返回 undefined。
const list = ['apple', 'banana', 'orange'];
// 删除末尾元素,并获取返回值
const removedItem = list.pop();
console.log(removedItem); // 示例输出: 'orange'
console.log(list); // 示例输出: ['apple', 'banana']
console.log(list.length); // 示例输出: 2
除了 pop(),直接缩减数组的 length 属性(例如 list.length = list.length - 1)也可以达到截断末尾的效果,但这种方式无法返回被移除的值,且代码意图不如 pop() 清晰,因此通常推荐使用 pop()。
2. 不可变操作:使用 array.slice(0, -1)
在 React 状态管理或遵循纯函数原则的场景下,直接修改原数组会导致外部依赖产生不可预期的副作用。此时可以使用 slice() 创建一个不包含末尾项的新数组。
const original = ['apple', 'banana', 'orange'];
// slice(0, -1) 截取从索引 0 到倒数第一个元素(不含倒数第一个)
const updated = original.slice(0, -1);
console.log(updated); // 示例输出: ['apple', 'banana']
console.log(original); // 示例输出: ['apple', 'banana', 'orange'] (原数组未改变)
如果运行环境支持 ECMAScript 2023(Node.js 20+ 或现代浏览器),还可以使用 original.toSpliced(-1, 1),它同样会返回一个剔除末尾项的新数组,原数组保持不变。
二、删除第一个元素(头部删除)
删除数组开头的元素常用于队列的“出队”(FIFO)操作或按序处理待办任务。
1. 原地修改:使用 array.shift()
shift() 是最标准的头部删除方法。它会移除索引为 0 的元素,并把后续所有元素往前移动一个位置填充空缺,最后返回被删除的元素。
const queue = ['task-1', 'task-2', 'task-3'];
// 移出并获取队列首项
const currentTask = queue.shift();
console.log(currentTask); // 示例输出: 'task-1'
console.log(queue); // 示例输出: ['task-2', 'task-3']
若对空数组调用 shift(),不会报错,而是安全地返回 undefined,数组长度依然为 0。
2. 不可变操作:使用 array.slice(1) 或数组解构
若希望得到剔除首项后的新数组而不破坏原始数组,slice(1) 是最简洁易读的写法:
const numbers = [10, 20, 30, 40];
// 截取索引 1 及之后的所有元素
const remaining = numbers.slice(1);
console.log(remaining); // 示例输出: [20, 30, 40]
console.log(numbers); // 示例输出: [10, 20, 30, 40]
在解构赋值场景中,使用剩余参数语法也能实现类似效果:
const [first, ...rest] = numbers;
console.log(first); // 示例输出: 10 (第一个元素)
console.log(rest); // 示例输出: [20, 30, 40] (其余元素组成的新数组)
三、底层机制差异:为什么 shift 慢于 pop?
在理解了具体调用方法后,深入了解底层的执行开销对编写高质量代码很有帮助。pop() 与 shift() 虽然同样用于删除单个元素,但两者的性能复杂度存在显著差异:
- pop() 的时间复杂度为 O(1): JavaScript 引擎维护着数组的连续存储空间与 length 属性。执行
pop()时,引擎只需定位到末尾索引,释放该槽位的值,并将 length 减 1。原数组中其它任意元素的内存地址和索引均无需发生变动。 - shift() 的时间复杂度为 O(n): 执行
shift()时,由于索引 0 的位置被腾出,数组为了维持连续的下标访问特性,必须将索引 1 到 n-1 的所有元素依次向左搬移一位,并在搬移完成后更新 length。
在普通数十项至数百项的小数组中,这种时间差异在毫秒级内微乎其微;但在包含数十万项的大数组或高频循环调用的情境下,连续调用 shift() 会引起明显的性能瓶颈。如果业务场景需要高频从头部弹出数据,应考虑使用双端队列(Deque)结构或指针计数,避免反复触发全量数组移动。
四、避坑指南:必须注意的边界情况
1. 切忌使用 delete 关键字删除首尾元素
初学者容易尝试使用 delete array[0] 或 delete array[array.length - 1]。在 JavaScript 中,delete 操作符仅将对应属性的值置为空洞(empty / undefined),并不会改变数组的 length,这会导致数组变成“稀疏数组”(Sparse Array),破坏数组的内存紧凑性,甚至导致后续 forEach、map 等方法跳过该槽位产生意外行为。
delete array[i] 来删除数组元素。请务必使用 pop()、shift() 或 splice() 等正规数组方法。
2. 类数组对象(Arguments、NodeList)的处理
函数中的 arguments 对象或 DOM 查询返回的 NodeList 属于“类数组”,它们拥有 length 属性,但并未继承 Array.prototype 上的 pop() 与 shift() 方法。若直接调用会抛出 TypeError。
处理类数组前,应先使用 Array.from() 或展开语法将其转换为真实数组:
function handleElements() {
// 转换为真实数组后再操作
const args = Array.from(arguments);
const first = args.shift();
const last = args.pop();
return { first, last, remaining: args };
}
五、核心方法快速查阅表
根据操作位置、是否改变原数组以及返回值类型,可参照下表快速选定合适的方法:
| 目标位置 | 推荐方法 | 是否原地修改 | 返回值 | 时间复杂度 | 典型适用场景 |
|---|---|---|---|---|---|
| 最后一个元素 | array.pop() |
是(破坏性) | 被删除的元素 | O(1) | 本地数据处理、栈操作、常规高性能删除 |
| 最后一个元素 | array.slice(0, -1) |
否(无副作用) | 剔除尾部后的新数组 | O(n) | React / Redux 状态更新、不可变数据流 |
| 第一个元素 | array.shift() |
是(破坏性) | 被删除的元素 | O(n) | 先进先出队列、简单消费型迭代 |
| 第一个元素 | array.slice(1) |
否(无副作用) | 剔除头部后的新数组 | O(n) | 函数式处理、参数列表截取、状态更新 |
| 任意首尾 | array.splice(start, 1) |
是(破坏性) | 包含被删元素的数组 | O(n) | 需要在删除的同时插入新元素或通用处理 |
总结而言,在追求性能和简单直观的脚本逻辑中,pop() 与 shift() 是最基础的内置工具;而在现代前端框架中,优先选用 slice() 或扩展语法创建新数组,能够有效保障组件状态的可预测性与可追溯性。
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
p5.js 中 setup() 和 draw() 函数的执行机制详解
2026-11-05 12:32:38








