JavaScript对象与数组操作语法速查:增删改查常用方法
针对JavaScript中对象(Object)与数组(Array)的增删改查(CRUD)操作,提供系统化的现代原生语法速查指南,包含破坏性变异与不可变操作对比、属性检测与删除陷阱、高频实用代码示例及全景架构总结。
一、对象(Object)的增删改查常用语法
JavaScript 对象是由键名(字符串或 Symbol)与值构成的无序集合。对于对象的增删改查,关键在于理解属性写入的优先级、键名存在性检测的范围,以及属性删除时的不可变考量。
1. 对象的“增”与“改”(Create & Update)
在 JavaScript 中,对象的“添加新属性”与“修改已有属性”在底层语法上是统一的:如果指定的键名在对象自身中尚不存在,则创建该属性;如果已经存在,则直接覆盖原值。
// 1. 点号表示法(静态键名)与方括号表示法(动态表达式键名)
const user = { id: 1001 };
user.name = "张伟"; // 增:静态属性添加
user["role"] = "管理员"; // 增:字符串键名
const dynamicKey = "loginCount";
user[dynamicKey] = 1; // 增:动态变量作为键名
user.name = "张大伟"; // 改:直接覆盖已有属性
// 2. 现代不可变合并:对象展开运算符(Object Spread)
const originalUser = { id: 1001, name: "张伟", role: "访客" };
const updatedUser = {
...originalUser,
role: "管理员", // 改:通过同名键覆盖
department: "研发部" // 增:追加新属性
};
// 3. Object.assign 浅拷贝写入目标对象
const target = { a: 1 };
Object.assign(target, { b: 2 }, { a: 99 }); // target 变为 { a: 99, b: 2 }
在 React、Vue 等现代框架的状态管理场景中,推荐优先使用对象展开运算符(...)构造全新对象,从而保持前序状态的纯净;而在底层高性能计算或仅在局部作用域封装对象时,直接使用点号赋值效率更高。
2. 对象的“查”(Retrieve & Inspection)
读取对象属性时,现代 JavaScript 提供了可选链(Optional Chaining)操作符 ?. 与空值合并操作符 ??,极大简化了深层嵌套属性的防御性检查。此外,判断一个键名是否存在是查操作中非常关键的步骤。
const profile = {
account: {
email: "zhangwei@example.com"
},
level: 0
};
// 1. 安全访问嵌套属性(避免 TypeError: Cannot read properties of undefined)
const userEmail = profile.account?.email; // "zhangwei@example.com"
const userCity = profile.account?.address?.city; // undefined,不报错
// 2. 结合空值合并操作符设置默认值(仅在 null 或 undefined 时生效)
const userLevel = profile.level ?? 1; // 0(保留数字 0,不会像 || 那样被转为默认值)
// 3. 判断属性是否存在(区分自身属性与原型链属性)
console.log(Object.hasOwn(profile, "level")); // true(仅检查自身,ES2022推荐写法)
console.log("toString" in profile); // true('in' 会检查继承自 Object.prototype 的方法)
console.log(Object.hasOwn(profile, "toString"));// false(自身未声明 toString)
// 4. 批量查询与遍历
const keys = Object.keys(profile); // ["account", "level"]
const values = Object.values(profile); // [{ email: "..." }, 0]
const entries = Object.entries(profile); // [["account", {...}], ["level", 0]]
3. 对象的“删”(Delete)
删除对象属性有两种主流做法:一种是通过原生 delete 操作符进行原地破坏性删除;另一种是通过解构赋值(Rest 语法)生成排除了目标属性的不可变新对象。
const settings = { theme: "dark", notifications: true, autoSave: false };
// 做法 A:原生 delete 操作符(破坏性原地删除)
delete settings.autoSave;
console.log(settings); // { theme: "dark", notifications: true }
console.log("autoSave" in settings); // false
// 做法 B:Rest 解构赋值(不可变非破坏性排除)
const rawData = { id: 101, passwordHash: "x8f2d", username: "alice" };
const { passwordHash, ...safeUserData } = rawData;
// safeUserData 为 { id: 101, username: "alice" },rawData 完全未受改变
二、数组(Array)的增删改查:变异与纯函数操作步骤
JavaScript 数组是有序的数字索引集合。在对数组进行增删改查时,最关键的维度是判断“是否允许改变原数组(In-place Mutation)”。根据此条件,方法被划分为直接修改原数组的变异方法与返回全新数组的纯函数方法。
1. 数组的“增”(Add / Insert)
向数组增加元素主要分为三种位置:尾部追加、头部插入、指定任意索引位置插入。
// --- 策略 A:修改原数组(变异) ---
const listA = [1, 2];
listA.push(3); // 尾部追加 -> listA 变为 [1, 2, 3],返回新长度 3
listA.unshift(0); // 头部插入 -> listA 变为 [0, 1, 2, 3],返回新长度 4
listA.splice(2, 0, 99); // 索引 2 位置插入 99 -> [0, 1, 99, 2, 3]
// --- 策略 B:不可变生成新数组(纯函数) ---
const listB = [1, 2];
const appendTail = [...listB, 3]; // [1, 2, 3],原 listB 不变
const prependHead = [0, ...listB]; // [0, 1, 2],原 listB 不变
const insertMiddle = listB.toSpliced(1, 0, 99); // [1, 99, 2](ES2023 新增不可变方法)
2. 数组的“删”(Remove / Delete)
从数组中移除元素同样依赖于具体的目标位置或筛选逻辑。
// --- 策略 A:修改原数组 ---
const fruits = ["苹果", "香蕉", "橙子", "西瓜"];
const last = fruits.pop(); // 移除尾部 -> 返回 "西瓜",fruits 变为 ["苹果", "香蕉", "橙子"]
const first = fruits.shift(); // 移除头部 -> 返回 "苹果",fruits 变为 ["香蕉", "橙子"]
fruits.splice(0, 1); // 按索引删除 1 项 -> fruits 变为 ["橙子"]
// --- 策略 B:不可变生成新数组 ---
const items = ["A", "B", "C", "D"];
// 1. 按位置删除:slice 截取
const withoutFirst = items.slice(1); // ["B", "C", "D"]
const withoutLast = items.slice(0, -1); // ["A", "B", "C"]
// 2. 按条件删除:filter 过滤
const filtered = items.filter(x => x !== "B"); // ["A", "C", "D"]
// 3. 按索引删除:toSpliced (ES2023)
const removedIndex1 = items.toSpliced(1, 1); // ["A", "C", "D"]
3. 数组的“改”(Update / Replace)
更新数组元素既包括单项精确定位修改,也包括对全量元素的格式化映射。
const scores = [80, 90, 85];
// 1. 原地修改单项
scores[1] = 95; // scores 变为 [80, 95, 85]
// 2. 不可变单项替换(ES2023 新增 arr.with)
const updatedScores = scores.with(0, 88); // 返回全新数组 [88, 95, 85],原 scores 不受影响
// 3. 批量条件更新:使用 map 映射生成新数组
const tasks = [
{ id: 1, title: "需求分析", done: false },
{ id: 2, title: "代码开发", done: false }
];
const nextTasks = tasks.map(task =>
task.id === 1 ? { ...task, done: true } : task
);
4. 数组的“查”(Retrieve & Search)
数组查找根据返回类型可分为:判断是否存在、返回索引位置、提取符合条件的完整元素对象,以及切片提取子数组。所有查找方法均不会改变原数组。
const list = [10, 25, 42, 55, 42, 88];
// 1. 存在性与基础索引查找
list.includes(42); // true
list.indexOf(42); // 2(返回首次出现的索引,未找到返回 -1)
list.lastIndexOf(42); // 4(从后向前找首次出现的索引)
// 2. 高级条件查找(传入回调函数)
const firstOver50 = list.find(num => num > 50); // 55(返回首个满足条件的元素)
const firstOver50Index = list.findIndex(num => num > 50);// 3(返回首个满足条件的索引)
const lastOver50 = list.findLast(num => num > 50); // 88(ES2023,从后向前查找)
// 3. 提取范围切片(非破坏性)
const subList = list.slice(1, 4); // [25, 42, 55](包含索引 1,不包含索引 4)
// 4. 整体规则验证
const hasEven = list.some(num => num % 2 === 0); // true(至少一个满足条件)
const allPositive = list.every(num => num > 0); // true(所有元素均满足条件)
三、常见误区与高频踩坑深度辨析
在实际项目中,有一些关于对象和数组的说法或写法流传甚广,看似合理有效,但在特定边界或严格校验下会导致隐匿的系统缺陷。
看似合理的理由:在读取时,obj.key === undefined 的结果与未声明该属性时读取到的值完全一致,表面上达到了“清空数据”的效果。
为什么需要修正:给属性赋值 undefined 只是更改了该属性的值,并没有注销该键名。此时通过 'key' in obj 检测依然会返回 true,且 Object.keys(obj)、JSON.stringify(obj) 或 for...in 依然会处理该键名槽位,甚至导致内存泄漏。如果确实需要移除属性,必须使用 delete obj.key 或解构语法。
看似合理的理由:在对象上使用 delete 能够清掉属性,而数组本身也是对象。
为什么需要修正:在数组元素上使用 delete arr[i] 并不会缩短数组长度(arr.length 保持不变),而是把该位置变成了空洞(Empty Slot),导致数组变成稀疏数组(Sparse Array)。后续使用 map、forEach 时,引擎会自动跳过空洞,造成迭代计数不一致。删除数组指定索引的元素,必须使用 splice(index, 1) 或 toSpliced(index, 1)。
看似合理的理由:执行 const clone = { ...obj } 后,给 clone 添加新属性不会影响 obj。
为什么需要修正:展开运算符 ... 与 Object.assign() 一样,只进行第一层浅拷贝(Shallow Copy)。如果属性的值是对象或数组,克隆出来的属性只复制了引用地址(Reference)。修改 clone.userInfo.city 会直接同步污染原对象 obj.userInfo.city。对于多层结构,必须进行多层嵌套展开,或者使用原生的 structuredClone(obj) 进行真实深拷贝。
四、对象与数组常用 CRUD 语法方法矩阵速查表
为了在日常编写代码时能够快速对照与决策,下表与全景图综合归纳了对象与数组在增删改查四个维度下的常用方法:
| 操作 | 对象(Object)推荐语法 | 数组(Array)原地变异方法 | 数组(Array)不可变纯函数方法 | 核心差异与注意事项 |
|---|---|---|---|---|
| 增 (Create) | obj.k = v{ ...obj, k: v } |
push(...items)(尾)unshift(...items)(头)splice(i, 0, x)(中) |
[...arr, x](尾)[x, ...arr](头)toSpliced(i, 0, x) |
变异方法修改原结构并返回长度;不可变方法返回包含新项的全新数组。 |
| 删 (Delete) | delete obj.kconst { k, ...rest } = obj |
pop()(尾)shift()(头)splice(i, count)(中) |
slice(1) / slice(0, -1)filter(predicate)toSpliced(i, count) |
严禁使用 delete arr[i] 制造稀疏空洞;过滤或解构能保持原数据不被污染。 |
| 改 (Update) | obj.k = newValObject.assign(obj, patch){ ...obj, ...patch } |
arr[i] = newValarr.sort()(原地排)arr.reverse()(原地翻) |
arr.with(i, newVal)arr.map(transform)arr.toSorted() / toReversed() |
ES2023 新增的 with() 彻底补齐了单项不可变修改的空白。 |
| 查 (Retrieve) | obj?.k ?? fallbackObject.hasOwn(obj, 'k')'k' in obj |
arr.includes(x) | arr.indexOf(x)arr.find(fn) | arr.findIndex(fn)arr.slice(start, end) | arr.at(-1) |
查询类方法均不会修改原数据。检测自身属性优先使用 Object.hasOwn。 |
|
什么时候该用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








