JavaScript 对象与解构

AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。

Node.js 22 LTS

对象是 JavaScript 里组织数据的主要方式。这一篇覆盖对象的基础操作、解构赋值,以及「改了一个对象,另一个也跟着变」这个高频问题的成因。

对象字面量与属性访问

const user = {
  name: "ann",
  age: 30,
  active: true,
};

两种访问方式:

user.name;         // 点号:键是合法标识符时用
user["name"];      // 方括号:键是变量、含空格或短横线时用
const key = "age";
user[key];         // 30
user["date-of-birth"];   // 点号写不了这种键

访问不存在的属性返回 undefined,不报错:

user.city;         // undefined
user.city.name;    // TypeError: Cannot read properties of undefined

简写与计算属性名

const name = "ann";
const age = 30;

const user = { name, age };          // 等价于 { name: name, age: age }

const field = "role";
const withRole = { name, [field]: "admin" };   // { name: "ann", role: "admin" }

方法也可以简写:

const counter = {
  count: 0,
  inc() {          // 等价于 inc: function () {}
    this.count += 1;
  },
};

注意方法简写是普通函数,this 指向调用者;写成箭头函数就不会有正确的 this,详见本系列第 4 篇。

增删改

const user = { name: "ann" };

user.age = 30;                 // 新增
user.name = "anna";            // 修改
delete user.age;               // 删除
Object.hasOwn(user, "age");    // false

判断属性是否存在用 Object.hasOwn(obj, key)(只查自身属性,不含原型链),不要用 key in obj 除非你确实想包含继承来的属性。

遍历对象

const user = { name: "ann", age: 30, active: true };

Object.keys(user);      // ["name", "age", "active"]
Object.values(user);    // ["ann", 30, true]
Object.entries(user);   // [["name", "ann"], ["age", 30], ["active", true]]

转成数组后用数组方法处理,这是最通用的写法:

Object.entries(user)
  .filter(([key, value]) => typeof value === "string")
  .map(([key, value]) => `${key}=${value}`);
// ["name=ann"]

Object.keys() 对字符串键按插入顺序返回,整数形式的键(如 "2")会被提前并按数值升序排列,这是规范行为,不要依赖它排序。

解构赋值

从对象里一次取出多个属性:

const user = { name: "ann", age: 30, city: "Shenzhen" };
const { name, age } = user;
console.log(name, age);   // "ann" 30

重命名与默认值:

const { name: userName, role = "user" } = user;
console.log(userName);   // "ann"
console.log(role);       // "user"(原对象没有 role)

嵌套解构:

const order = { id: 1, buyer: { name: "bob" } };
const { buyer: { name } } = order;   // name = "bob"

嵌套解构时若中间层为 null / undefined 会抛错,配合可选链和默认值更安全:

const { buyer: { name } = {} } = order;

数组也能解构,常用于交换与跳过:

const [first, second] = [1, 2, 3];
const [, , third] = [1, 2, 3];       // 3,跳过前两个
[first, second] = [second, first];   // 交换

函数参数解构见本系列第 4 篇。

展开运算符与浅拷贝

const base = { name: "ann", role: "user" };
const admin = { ...base, role: "admin" };
console.log(admin);   // { name: "ann", role: "admin" }
console.log(base);    // { name: "ann", role: "user" } 原对象不变

后面的属性覆盖前面的,这是「基于原对象生成新对象」的标准写法,也是 React 等框架里更新状态的基础模式。

但展开是浅拷贝,嵌套对象仍然是共享引用:

const user = { name: "ann", address: { city: "Shenzhen" } };
const copy = { ...user };

copy.address.city = "Beijing";
console.log(user.address.city);   // "Beijing" 原对象也被改了

需要完全独立时用 structuredClone()(Node 17+ / 现代浏览器支持):

const deepCopy = structuredClone(user);
deepCopy.address.city = "Beijing";
console.log(user.address.city);   // "Shenzhen"

对象引用:赋值不复制

const a = { count: 1 };
const b = a;
b.count = 2;
console.log(a.count);   // 2

b = a 复制的是引用,两个变量指向同一个对象。比较对象时同理,比较的是引用:

{ count: 1 } === { count: 1 }   // false,两个不同对象
a === b                          // true,同一个对象

需要按内容比较时,要么逐字段比,要么用 JSON.stringify 做粗略比较(键顺序敏感,不推荐用于复杂结构)。

冻结与不可变

const config = Object.freeze({ retries: 3 });
config.retries = 5;      // 严格模式下抛 TypeError,静默失败时也不生效
console.log(config.retries);   // 3

Object.freeze() 只冻结一层(浅冻结),嵌套对象仍可改;且它不阻止你用 { ...config, retries: 5 } 生成新对象——这正是不可变更新想要的效果。

常见错误与注意事项

  1. 用 = 赋值对象后修改,以为原对象不受影响:赋值是共享引用,要独立得用展开或 structuredClone()。
  2. 展开运算符当深拷贝用:嵌套层仍共享,深层修改会互相影响。
  3. 解构不存在的嵌套属性:const { a: { b } } = obj 在 a 为 undefined 时抛错,加默认值 = {}。
  4. 用 for...in 遍历对象不过滤原型属性:用 Object.keys() / Object.entries()。
  5. 两个对象用 === 比较内容:永远是 false,除非是同一个引用。

小结

  • 属性访问:点号用于固定标识符,方括号用于变量键。
  • 遍历用 Object.keys() / values() / entries(),转成数组后用数组方法。
  • 解构可取属性、重命名、给默认值。
  • 展开是浅拷贝,深拷贝用 structuredClone()。
  • 对象赋值共享引用,=== 比较的是引用不是内容。

下一步