JavaScript数组增删改查操作代码实例详解

作者:小括号

时间:2026-10-30

浏览:188

全面详解JavaScript数组的增加、删除、修改与查找(CRUD)核心操作,包含就地修改与不可变处理对比、最新ES规范方法及实用代码示例。

文章目录

在前端交互与服务端数据处理中,数组是最基础、使用频率最高的数据容器。假设正在开发一个电商购物车或后台用户权限管理系统,用户加入商品、剔除失效条目、修改购买数量、检索特定编号的记录,本质上全部对应着数组的增、删、改、查(CRUD)操作。JavaScript 提供了丰富的内置数组原型方法,但在实际业务开发中,开发者不仅要掌握方法的参数与返回值,更需要清晰区分哪些方法会就地修改原数组(Mutating),哪些方法会返回纯净的新数组(Immutable)。本文将围绕这一核心逻辑,逐项剖析常用与现代 ES 标准中的增删改查方法,并给出可直接运行的完整代码实例。

一、增加(Create / Insert)操作

向数组中追加或插入元素时,常见场景包括末尾追加、头部插入以及在指定下标位置插入。不同方法对原数组的影响和执行开销各有不同。

1. 尾部添加:push() 与 扩展运算符

push() 会将一个或多个元素添加到数组的末尾,并返回修改后数组的新长度。该方法会直接修改原数组,时间复杂度通常为 O(1)。若需要保持原数组不变,可以使用 ES6 的扩展运算符(Spread Syntax)。

// 方式一:push 直接修改原数组
const listA = ['Apple', 'Banana'];
const newLen = listA.push('Orange', 'Mango');

console.log(newLen); // 示例输出: 4
console.log(listA);  // 示例输出: ['Apple', 'Banana', 'Orange', 'Mango']

// 方式二:扩展运算符(不修改原数组)
const baseList = ['Apple', 'Banana'];
const resultList = [...baseList, 'Orange'];

console.log(baseList);   // 示例输出: ['Apple', 'Banana'](原数组未受影响)
console.log(resultList); // 示例输出: ['Apple', 'Banana', 'Orange']

2. 头部插入:unshift() 与 前置扩展

unshift() 将新元素插入到数组起始位置,原所有元素的下标依次向后平移,返回值为新长度。由于涉及整体索引重排,处理大数据量时开销较大(O(n))。同样,前置扩展可用于纯函数式场景。

// 方式一:unshift 就地插入
const queue = ['Task2', 'Task3'];
queue.unshift('Task1');
console.log(queue); // 示例输出: ['Task1', 'Task2', 'Task3']

// 方式二:前置扩展生成新数组
const rawQueue = ['Task2', 'Task3'];
const updatedQueue = ['UrgentTask', ...rawQueue];
console.log(updatedQueue); // 示例输出: ['UrgentTask', 'Task2', 'Task3']

3. 指定位置插入:splice() 与 toSpliced()

要在特定索引处插入元素,传统做法是调用 splice(startIndex, deleteCount, ...items),并将 deleteCount 设为 0。ECMAScript 2023 正式引入了 toSpliced(),它接收相同参数,但返回新数组,绝不改动原数组。

// splice:在索引 1 处插入两项(修改原数组)
const letters = ['A', 'D'];
letters.splice(1, 0, 'B', 'C');
console.log(letters); // 示例输出: ['A', 'B', 'C', 'D']

// toSpliced:在索引 1 处插入两项(返回新数组,ES2023)
const origin = ['A', 'D'];
const splicedCopy = origin.toSpliced(1, 0, 'B', 'C');
console.log(origin);      // 示例输出: ['A', 'D']
console.log(splicedCopy); // 示例输出: ['A', 'B', 'C', 'D']
JavaScript数组增删改查操作代码实例详解
图 1:JavaScript 数组增加元素操作与底层索引移动机制

二、删除(Delete / Remove)操作

删除操作在业务中通常分为两类:按特定物理位置(首尾、特定下标)移除,以及根据业务判定条件(如特定 ID 或过期状态)过滤。

1. 移除首尾:pop() 与 shift()

pop() 移除并返回数组的最后一个元素,开销为 O(1);shift() 移除并返回数组的首个元素,之后所有元素前移一位,开销为 O(n)。两者均直接改变原数组。如果数组为空,二者均返回 undefined。

const cart = ['Phone', 'Tablet', 'Watch'];

const lastItem = cart.pop();
console.log(lastItem); // 示例输出: 'Watch'
console.log(cart);     // 示例输出: ['Phone', 'Tablet']

const firstItem = cart.shift();
console.log(firstItem); // 示例输出: 'Phone'
console.log(cart);      // 示例输出: ['Tablet']

2. 按下标区间删除:splice() 与 slice()

splice(startIndex, deleteCount) 会从原数组中切除指定数量的元素,并将被删除的元素以新数组的形式返回。如果不希望改动原数组,可结合 slice() 截取前后段拼合,或直接使用 ES2023 的 toSpliced(startIndex, deleteCount)。

// 方式一:splice 就地切除
const numbers = [10, 20, 30, 40, 50];
const removed = numbers.splice(1, 2); // 从索引 1 开始删除 2 个元素

console.log(removed); // 示例输出: [20, 30]
console.log(numbers); // 示例输出: [10, 40, 50]

// 方式二:toSpliced 纯函数式删除(原数组不变)
const raw = [10, 20, 30, 40, 50];
const kept = raw.toSpliced(1, 2);
console.log(raw);  // 示例输出: [10, 20, 30, 40, 50]
console.log(kept); // 示例输出: [10, 40, 50]

3. 条件批量删除:filter()

当删除目标取决于特定属性(如删除已下架商品或指定用户 ID)时,使用 filter() 是工业界最推荐的标准做法。它遍历数组,仅将测试函数返回 true 的元素收录至新数组中,完全不产生副作用。

const userList = [
  { id: 101, name: 'Alice', active: true },
  { id: 102, name: 'Bob', active: false },
  { id: 103, name: 'Charlie', active: true }
];

// 删除 id 为 102 的用户(即保留 id !== 102 的条目)
const activeUsers = userList.filter(user => user.id !== 102);

console.log(activeUsers.length); // 示例输出: 2
console.log(userList.length);    // 示例输出: 3(原数组完好)
JavaScript数组增删改查操作代码实例详解
图 2:JavaScript 数组删除操作执行流程与决策路径

三、修改(Update / Replace)操作

修改操作的核心是在原位或在新数组中赋予目标项新的值。在现代前端框架中,修改是产生数据 Bug 最频繁的区域。

1. 直接根据下标赋值

最直接的方式是通过方括号索引直接更新。这种操作速度极快,但仅在明确知道下标且允许修改原引用的局部函数内部使用。

const scores = [85, 90, 78];
scores[2] = 95; // 将索引 2 的数值修改为 95
console.log(scores); // 示例输出: [85, 90, 95]

2. 现代不可变下标赋值:with() 方法

ECMAScript 2023 新增的 with(index, value) 方法弥补了传统下标赋值必须修改原对象的不足。它返回一个新数组,其中指定索引的值被更新,而原数组保持完好。

const tags = ['js', 'react', 'node'];
const updatedTags = tags.with(1, 'vue');

console.log(tags);        // 示例输出: ['js', 'react', 'node']
console.log(updatedTags); // 示例输出: ['js', 'vue', 'node']

3. 对象数组的映射更新:map()

当处理复杂业务实体(如更新购物车某项的 quantity)时,推荐使用 map() 遍历并结合对象解构创建新对象,实现彻底的不可变更新。

const orders = [
  { id: 1, title: 'Book', count: 1 },
  { id: 2, title: 'Pen', count: 2 }
];

// 业务需求:将 id 为 2 的商品数量增加 1
const newOrders = orders.map(item => {
  if (item.id === 2) {
    return { ...item, count: item.count + 1 }; // 返回新对象引用
  }
  return item;
});

console.log(newOrders[1].count); // 示例输出: 3
console.log(orders[1].count);    // 示例输出: 2(原对象完好)
JavaScript数组增删改查操作代码实例详解
图 3:原数组就地修改与不可变更新机制对比

四、查找与读取(Read / Query)操作

检索是数组读取逻辑的核心。根据需要的是布尔存在性、元素下标、元素本身还是所有匹配结果,JavaScript 提供了针对性的解法。

1. 索引访问与现代 at() 方法

常规使用 arr[index] 访问元素。当需要倒序获取元素(如倒数第一个)时,传统写法 arr[arr.length - 1] 较为繁琐,ES2022 提供的 at() 支持传入负数索引。

const versions = ['v1.0', 'v1.1', 'v2.0', 'v3.2'];

console.log(versions.at(0));  // 示例输出: 'v1.0'
console.log(versions.at(-1)); // 示例输出: 'v3.2'(取末位)
console.log(versions.at(-2)); // 示例输出: 'v2.0'(取倒数第二位)

2. 基础值匹配:includes() 与 indexOf()

用于简单原始类型(数字、字符串)的精准查找。includes() 返回布尔值,且能准确识别 NaN;indexOf() 返回匹配项索引,找不到则返回 -1。

const statusCodes = [200, 301, 404, 500];

console.log(statusCodes.includes(404)); // 示例输出: true
console.log(statusCodes.includes(403)); // 示例输出: false
console.log(statusCodes.indexOf(301));  // 示例输出: 1

3. 条件匹配:find(), findIndex() 与 findLast()

对于对象数组,必须使用传入判定函数的谓词检索。find() 返回首个匹配项实体(未命中返回 undefined),findIndex() 返回首个匹配项索引(未命中返回 -1)。ES2023 新增的 findLast() 与 findLastIndex() 则从数组尾部开始倒序查找,大幅提升了在追加型日志记录中的检索效率。

const logs = [
  { id: 1, level: 'info', msg: 'Server start' },
  { id: 2, level: 'error', msg: 'Timeout' },
  { id: 3, level: 'info', msg: 'Heartbeat' },
  { id: 4, level: 'error', msg: 'Disk full' }
];

// 顺序查找首个 error
const firstErr = logs.find(item => item.level === 'error');
console.log(firstErr.msg); // 示例输出: 'Timeout'

// 倒序查找最近发生的一次 error(ES2023)
const latestErr = logs.findLast(item => item.level === 'error');
console.log(latestErr.msg); // 示例输出: 'Disk full'

4. 状态断言:some() 与 every()

用于集合逻辑校验。some() 只要有一项满足就返回 true,具有短路特性;every() 必须所有项均满足才返回 true。

const fileSizes = [12, 45, 88, 120]; // 单位 MB

// 是否存在超出 100MB 的大文件?
const hasOverLimit = fileSizes.some(size => size > 100);
console.log(hasOverLimit); // 示例输出: true

// 是否全部文件均小于 200MB?
const allValid = fileSizes.every(size => size < 200);
console.log(allValid); // 示例输出: true

五、核心方法分类与技术选型全景总结

为了让开发者在编写日常业务逻辑时能够快速决断,下表整理了核心 CRUD 方法的突变特性、返回值以及推荐场景:

操作类别 常用方法 是否就地修改原数组 主要返回值 典型适用场景
增 (Create) push() / unshift() 是(Mutating) 添加后数组新长度 高性能局部循环构建、队列/栈数据收集
[...arr, item] 否(Immutable) 全新浅拷贝数组 React/Vue 状态追加更新
splice() vs toSpliced() 前者是 / 后者否 切除项数组 / 全新插入后数组 特定中间位点插值
删 (Delete) pop() / shift() 是(Mutating) 被剔除的元素值 任务调度、双端队列消费
splice(idx, cnt) 是(Mutating) 被移除元素的数组 已知精确物理索引的就地移除
filter() 否(Immutable) 留存元素组成的新数组 按业务属性批量剔除特定数据
改 (Update) arr[i] = val 是(Mutating) 所赋的新值 纯算法计算、无需监听变更的原地交换
map() / with() 否(Immutable) 修改后对应的新数组 响应式对象属性更改、单点值纯函数替换
查 (Read) includes() / indexOf() 否(无副作用) 布尔值 / 索引整数 简单原始类型是否存在或定界
find() / findLast() 否(无副作用) 首个或末个匹配的对象实体 基于复杂业务逻辑的单条数据精确定位
避坑提醒:在循环中直接使用 splice() 删除项的索引偏移隐患

当使用递增的 for (let i = 0; i < arr.length; i++) 遍历数组并调用 splice(i, 1) 时,由于被删除项后面的所有元素会自动向左位移一位,导致下一个紧挨着的元素正好移动到了当前索引 i,而在下一轮循环中 i++ 会直接跳过该元素,造成连续重复项漏删。解决此问题有两种正确做法:要么使用倒序循环 for (let i = arr.length - 1; i >= 0; i--),要么直接改用 arr.filter() 构造新数组。

JavaScript数组增删改查操作代码实例详解
图 4:JavaScript 数组 CRUD 核心方法技术选型矩阵

掌握 JavaScript 数组的增删改查不仅需要熟悉常用语法,更要在代码编写之初清晰了解方法对原数组的改动性质。在局部计算和算法实现中,就地修改方法如 push、pop、splice 能提供极高的执行效率;而在现代前端应用与状态管理框架中,采用 filter、map、扩展运算符以及 toSpliced、with 等纯函数不可变方法,能有效保证数据流的可预测性并规避复杂的副作用渲染 Bug。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章