详细讲解在 JavaScript 中如何按索引删除数组中某个位置的元素,涵盖 splice 原地删除操作与避坑要点、delete 关键字导致稀疏数组的常见误区、toSpliced 与 slice 的不可变删除方案,以及负索引与循环删除的边界处理。
JS数组怎么按索引删除某个位置的元素
时间:2026-11-03
浏览:172
文章目录
array.splice(index, 1)。它会直接修改原数组,将指定索引处的元素移出,并将其后面的所有元素自动向前平移以填补空位。若业务场景要求保持原数据不变(例如在 React、Vue 3 或状态流转中),则可以使用 ES2023 推出的 array.toSpliced(index, 1),或者采用 slice() 搭配展开运算符,安全地返回一个排除了目标元素的新数组。
一、标准原地删除:使用 splice() 方法
在无需保留原数组引用的普通开发场景中,Array.prototype.splice() 是 JavaScript 官方提供的标准原地修改方法。
1. 基本语法与调用方式
splice() 接收多个参数,在按索引删除单项元素时,核心关注前两个参数:
const removedItems = array.splice(startIndex, deleteCount);
- startIndex(起始索引):从哪个位置开始操作(从 0 开始计数)。
- deleteCount(删除数量):从起始索引开始连续删除多少个元素。若只需删除当前位置的一个元素,必须显式传入 1。
- 返回值:包含被删除元素的新数组。如果不需要接收被删除的项,可直接忽略返回值。
2. 完整代码示例
// 示例 1:删除索引为 2 的元素(即第三个元素 'Cherry')
const fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
// 执行删除:从索引 2 开始,删除 1 个元素
const deleted = fruits.splice(2, 1);
console.log(fruits);
// 示例输出:['Apple', 'Banana', 'Date', 'Elderberry']
console.log(deleted);
// 示例输出:['Cherry'](被删除的元素存放在数组中返回)
console.log(fruits.length);
// 示例输出:4(数组长度随之减少 1)
如果调用时写成 fruits.splice(2) 而遗漏了 deleteCount,JavaScript 的默认行为是将索引 2 及其之后直到末尾的所有元素全部删除!因此,按索引删除单个元素时务必写全 splice(index, 1)。
二、常见误区:为什么不能使用 delete 关键字
不少刚接触 JavaScript 的开发者会联想到对象的属性删除语法 delete obj.prop,并尝试对数组执行 delete array[index]。这种做法在数组上通常是错误的。
1. delete 对数组的作用机理
在 JavaScript 中,数组本质上是特殊的对象,数字索引就是其属性名。使用 delete array[index] 只会将该索引属性从对象上移除,并把该位置置为“空槽(empty slot / hole)”,而不会调整数组的长度,也不会让后面的元素向前平移。
const numbers = [10, 20, 30, 40];
// 错误示范:使用 delete 关键字
delete numbers[1];
console.log(numbers);
// 示例输出:[10, empty, 30, 40]
console.log(numbers.length);
// 示例输出:4(长度并未减少!)
console.log(numbers[1]);
// 示例输出:undefined
2. 稀疏数组(Sparse Array)带来的隐患
执行 delete 后,原本紧凑密集的数组变成了稀疏数组。这会导致常规的高阶遍历方法出现反直觉的表现:
forEach()、map()、filter()等迭代器在遍历时会自动跳过空槽,导致回调执行次数少于length。JSON.stringify()序列化时,空槽会被强制转换为null,造成数据格式被污染。- 现代 JavaScript 引擎(如 V8)会对密集数组进行底层布局优化,出现稀疏数组会使引擎退回到哈希字典模式,影响访问性能。
三、不可变删除:返回全新数组的三种方案
在现代前端应用(如 React 状态管理、Redux、Vue 3 计算属性等)中,为了遵循不可变数据(Immutability)原则,开发者往往要求不修改原始数组,而是生成一个去除了目标索引项的新数组。以下是三种主流的不可变删除手段:
方案 1:toSpliced() 原生方法(现代标准,ES2023+)
ECMAScript 2023 正式引入了 Array.prototype.toSpliced()。它的参数形式与 splice() 完全一致,但区别在于它不会修改原数组,而是直接返回修改后的全新数组副本。
// 推荐方案(Node.js 20+、Chrome 110+、Safari 16+)
const list = ['Vue', 'React', 'Angular', 'Svelte'];
// 删除索引为 1 的项('React'),返回新数组
const updatedList = list.toSpliced(1, 1);
console.log(updatedList);
// 示例输出:['Vue', 'Angular', 'Svelte']
console.log(list);
// 示例输出:['Vue', 'React', 'Angular', 'Svelte'](原数组完好无损)
方案 2:slice() 与展开运算符合并(全环境通用)
如果运行环境需要兼容较早版本的浏览器或低版本 Node.js,可以使用 Array.prototype.slice()。通过分别截取目标索引前、后的两个片段,再使用展开运算符(...)拼接为一个新数组:
const original = ['A', 'B', 'C', 'D'];
const removeIndex = 2;
// 前半段:[0, 2) 即 ['A', 'B'];后半段:[3, 4) 即 ['D']
const result = [
...original.slice(0, removeIndex),
...original.slice(removeIndex + 1)
];
console.log(result); // 示例输出:['A', 'B', 'D']
console.log(original); // 示例输出:['A', 'B', 'C', 'D']
这种方案在 ES6 及以上环境中完全原生支持,无需任何 Polyfill,执行性能非常优异。
方案 3:filter() 按索引排除(函数式过滤)
Array.prototype.filter() 的回调函数第二个参数会传入当前项的索引。只需判断索引是否不等于目标值即可:
const tags = ['HTML', 'CSS', 'JavaScript'];
const targetIndex = 0;
const filteredTags = tags.filter((_, index) => index !== targetIndex);
console.log(filteredTags); // 示例输出:['CSS', 'JavaScript']
filter() 书写简短,但需要注意:它会对数组中的每一个元素都执行一次判断,时间复杂度恒定为 O(N)。在对百万级超大数组进行单项删除时,其耗时会高于 slice 和 toSpliced。但在普通业务数组中,性能差异微乎其微。
四、边界场景与容易出错的细节
在实际工程中,索引值往往来自前端计算、路由参数或用户交互输入。处理以下边界条件可避免隐蔽的 Bug:
1. 支持负数索引的特性
splice() 和 toSpliced() 原生支持负数索引。当传入负数时,它表示从数组末尾倒数定位:
const items = ['first', 'second', 'third', 'last'];
// -1 表示倒数第一个元素
items.splice(-1, 1);
console.log(items); // 示例输出:['first', 'second', 'third']
// -2 表示倒数第二个元素
items.splice(-2, 1);
console.log(items); // 示例输出:['first', 'third']
2. 索引越界时的表现
- 大于或等于数组长度:若
index >= arr.length,splice(index, 1)不会报错,也不会删除任何元素,原数组保持不变。 - 极小负数:若负数的绝对值大于数组长度(例如数组长 3,传入 -10),操作位置会被自动钳制在索引 0 处开始执行。
3. 在循环中按索引删除多个元素的“跳跃陷阱”
如果在循环中多次调用 splice() 删除元素,必须格外警惕:由于每次删除都会导致后续元素向前移动,正序递增的循环变量(i++)会漏掉紧随其后的下一个元素。
// ❌ 错误示范:正向循环删除
const nums = [1, 2, 2, 3];
for (let i = 0; i < nums.length; i++) {
if (nums[i] === 2) {
nums.splice(i, 1); // 删除后第二个 2 移到了当前 i 的位置,下一轮 i++ 会直接跳过它!
}
}
console.log(nums); // 示例输出:[1, 2, 3](遗漏了一个 2)
// ✔ 正确做法 1:从后往前倒序循环(索引前移不影响未处理的前半部分)
for (let i = nums.length - 1; i >= 0; i--) {
if (nums[i] === 2) {
nums.splice(i, 1);
}
}
// ✔ 正确做法 2:优先采用 filter() 批量排除,避免循环维护索引
const cleaned = nums.filter(n => n !== 2);
五、各删除方案特性对比与技术选型
为了方便在实际开发中快速选用恰当的方法,下表汇总了四种主要删除方式的对比:
| 方法名称 | 是否修改原数组 | 返回值 | 环境兼容性 | 典型推荐场景 |
|---|---|---|---|---|
splice(i, 1) |
是(原地修改) | 被删除项数组 | 所有 JS 环境 | 局部临时变量、算法题、内部缓存等追求极高执行效率与低内存开销的场景 |
toSpliced(i, 1) |
否(纯函数) | 排除目标项的新数组 | ES2023+ (Node 20+) | 现代 React/Vue 状态更新、前端工程化项目、追求代码语义规范的场景 |
slice + 展开 |
否(纯函数) | 排除目标项的新数组 | ES6+ (所有主流环境) | 需要不可变特性、但项目需要兼顾老旧运行环境(如旧版 WebView 或低版本 Node) |
filter((_, idx) => ...) |
否(纯函数) | 符合条件的新数组 | ES5+ (所有主流环境) | 根据多个条件复合过滤、或者根据元素内容与索引联合判断的场景 |
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。