JavaScript 对象
对象是 JS 里最重要的数据结构——它用"键值对"组织数据,几乎一切皆对象:用户、商品、配置、API 返回值,甚至函数、数组本身也是对象。这一章我们彻底搞懂对象的创建、操作、复制、方法。
1. 对象基础
对象用 {} 创建,里面是"键: 值"对,逗号分隔。键必须是字符串(或 Symbol),值可以是任何类型:
// 对象字面量:键值对的集合
const user = {
name: "小明",
age: 20,
isStudent: true,
hobbies: ["读书", "写代码"]
};
// 1. 点语法访问属性
console.log(user.name); // "小明"
console.log(user.age); // 20
// 2. 方括号语法(键是变量或特殊字符时用)
console.log(user["name"]); // "小明"
const key = "age";
console.log(user[key]); // 20
// 3. 修改属性
user.age = 21;
user.city = "北京"; // 添加新属性
delete user.isStudent; // 删除属性
// 4. 检查属性是否存在
console.log("name" in user); // true
console.log(user.hasOwnProperty("name")); // true访问属性有两种方式:
- 点语法
obj.name:键名固定时用,简洁。 - 方括号
obj["name"]:键名是变量、包含特殊字符、或动态计算时用。
不存在的属性返回 undefined(不报错),这是 JS 对象的一个特殊行为。
2. ES6 对象简写与计算属性
// 属性简写:变量名和键名相同时
const name = "小明";
const age = 20;
const user = { name, age }; // 等价于 { name: name, age: age }
console.log(user); // { name: "小明", age: 20 }
// 方法简写(更简洁)
const calc = {
add(a, b) { return a + b; },
sub(a, b) { return a - b; }
};
// 等价于
const calc2 = {
add: function (a, b) { return a + b; },
sub: function (a, b) { return a - b; }
};
// 计算属性名(键名是表达式)
const field = "score";
const student = {
name: "小红",
[field]: 95, // 动态键名
[`total${field}`]: "优秀" // 模板字符串键
};
console.log(student.score); // 95当变量名和键名相同时,可以只写一个;方法可以省略 function 关键字。这些简写在现代 JS 里大量使用,尤其 React 组件、对象配置等场景。
3. 对象解构赋值
解构是从对象里"挑出"需要的字段,赋给变量。这是 ES6 最常用的特性之一:
// 对象解构:从对象中"挑出"属性
const user = { name: "小明", age: 20, city: "北京" };
// 基本解构
const { name, age } = user;
console.log(name, age); // 小明 20
// 重命名(原属性名:新名)
const { name: userName } = user;
console.log(userName); // 小明
// 默认值(属性不存在时使用)
const { phone = "未填写" } = user;
console.log(phone); // 未填写
// 嵌套解构
const person = {
info: { name: "小明", address: { city: "北京" } }
};
const { info: { address: { city } } } = person;
console.log(city); // 北京
// 函数参数解构(超常用)
function greet({ name, age = 18 }) {
console.log(`你好 ${name},${age} 岁`);
}
greet({ name: "小明" }); // 你好 小明,18 岁解构在函数参数里特别有用——可以模拟命名参数,调用时不需要记参数顺序,默认值也清晰。React 的 props、各种 API 都大量使用。
2.5 实用:可选链 ?.
const user = { name: "小明", address: { city: "北京" } };
// ❌ 老写法:层层判断
const zip = user && user.address && user.address.zip;
// ✅ 可选链:任何一层是 null/undefined 就返回 undefined,不报错
const zip2 = user?.address?.zip;
console.log(zip2); // undefined(不报错)
// 也可以用在方法上
const result = obj?.method?.();
// 等价于:如果 obj 存在且 obj.method 是函数,才调用
// 数组下标也支持
const first = arr?.[0];4. 对象方法与 this
对象里可以放函数,称为"方法"。方法内的 this 指向调用对象:
// 对象方法中的 this
const user = {
name: "小明",
hobbies: ["读书", "写代码"],
// 方法:用普通函数,this 指向调用对象
greet() {
console.log(`我是 ${this.name}`);
},
// ❌ 箭头函数:this 是外层的(window/undefined)
badGreet: () => {
console.log(this.name); // undefined
},
// 方法里嵌套回调要用箭头函数,才能拿到正确的 this
showHobbies() {
this.hobbies.forEach(h => {
console.log(`${this.name} 喜欢 ${h}`);
});
}
};
user.greet(); // 我是小明
user.badGreet(); // undefined
user.showHobbies(); // 小明 喜欢 读书 / 小明 喜欢 写代码箭头函数与对象方法的搭配坑:对象方法用普通函数(greet() {}),this 是对象;但方法内部的回调用箭头函数,才能保留外层 this。这条规则记牢。
5. 展开运算符 ...
... 是复制和合并对象的利器:
// 展开运算符 ...:复制和合并对象(浅拷贝)
const user = { name: "小明", age: 20 };
// 复制对象(浅拷贝)
const copy = { ...user };
copy.name = "小红";
console.log(user.name); // 仍然是"小明"(不影响原对象)
// 合并对象
const defaults = { theme: "light", font: "sans" };
const userPrefs = { theme: "dark" };
const merged = { ...defaults, ...userPrefs };
console.log(merged); // { theme: "dark", font: "sans" }(后者覆盖前者)
// 添加 / 覆盖属性
const updated = { ...user, age: 21, city: "北京" };
console.log(updated);
// { name: "小明", age: 21, city: "北京" }注意:展开是浅拷贝——只复制一层,嵌套的对象仍是引用(下面会讲)。
6. Object 静态方法
// Object 静态方法
const user = { name: "小明", age: 20, city: "北京" };
// 1. Object.keys():所有键组成的数组
console.log(Object.keys(user)); // ["name", "age", "city"]
// 2. Object.values():所有值组成的数组
console.log(Object.values(user)); // ["小明", 20, "北京"]
// 3. Object.entries():键值对组成的二维数组
console.log(Object.entries(user));
// [["name","小明"], ["age",20], ["city","北京"]]
// 4. 遍历对象(经典写法)
for (const [key, value] of Object.entries(user)) {
console.log(key, value);
}
// 5. Object.assign():合并对象(等价于展开运算符)
const merged = Object.assign({}, { a: 1 }, { b: 2 });
console.log(merged); // { a: 1, b: 2 }
// 6. Object.freeze():冻结对象,不可改
const config = Object.freeze({ host: "localhost" });
config.host = "x"; // 静默失败 / 严格模式报错Object.keys/values/entries 是遍历对象的标准三件套——因为 for-in 会带上继承的属性,而这套方法只返回对象自身的可枚举属性,更安全。
7. 浅拷贝 vs 深拷贝
这是 JS 最经典的坑之一:对象是引用类型,赋值只是复制地址,改一边影响另一边:
// 浅拷贝 vs 深拷贝
const obj = {
name: "小明",
hobbies: ["读书", "写代码"],
info: { age: 20 }
};
// 1. 浅拷贝:只复制一层
const shallow1 = { ...obj };
const shallow2 = Object.assign({}, obj);
shallow1.hobbies.push("游泳");
console.log(obj.hobbies); // ["读书","写代码","游泳"] ⚠️ 原对象也被改了!
// 2. 深拷贝:全部复制,互不影响
// 方法 A:JSON(简单但会丢失函数、Date、undefined 等)
const deep1 = JSON.parse(JSON.stringify(obj));
// 方法 B:structuredClone(推荐,现代浏览器都支持)
const deep2 = structuredClone(obj);
deep2.hobbies.push("游泳");
console.log(obj.hobbies); // ["读书","写代码"](原对象不受影响)- 浅拷贝:只复制一层。展开
...、Object.assign({}, obj)、Array.from()都是浅拷贝。 - 深拷贝:递归复制所有层级。
JSON.parse(JSON.stringify())简单但有局限(丢函数、Date 变字符串、循环引用报错);structuredClone()是现代推荐方案。
8. 原型链入门
JS 的继承靠原型链。每个对象都有一个隐藏属性 __proto__,指向它的"原型对象"。访问属性时,先在对象本身找,找不到就去原型找,层层向上。
const obj = { name: "小明" };
// obj 自己没有 toString,但它从 Object.prototype 继承了
console.log(obj.toString()); // "[object Object]"
console.log(obj.__proto__ === Object.prototype); // true
console.log(obj.__proto__.hasOwnProperty("toString")); // true
// 原型链示意:
// obj --> Object.prototype --> null
// 数组原型链:
// [] --> Array.prototype --> Object.prototype --> null
console.log([1,2,3].__proto__ === Array.prototype); // true原型链就是 JS 实现"继承"的机制——你调用 [1,2,3].map() 时,map 不在数组本身,而在 Array.prototype。理解这点,才能读懂各种继承模式、框架源码。
9. 实战:对象数组操作
const users = [
{ name: "小明", age: 20, score: 88 },
{ name: "小红", age: 22, score: 95 },
{ name: "小刚", age: 19, score: 76 }
];
// 找到成绩最高的人
const top = users.reduce((max, u) => u.score > max.score ? u : max);
console.log(top.name); // 小红
// 筛选成年人
const adults = users.filter(u => u.age >= 20);
console.log(adults.length); // 2
// 取出所有名字组成新数组
const names = users.map(u => u.name);
console.log(names); // ["小明","小红","小刚"]
// 按成绩排序(注意 sort 会改原数组)
const sorted = [...users].sort((a, b) => b.score - a.score);
console.log(sorted[0].name); // 小红"对象数组"是真实业务里最高频的数据结构——API 返回的列表、表格数据都是这种。熟练用 map/filter/reduce 操作它,等于掌握了 JS 一半的实战能力。
小结
- 对象是键值对的集合,用
{}创建。 - 访问用点或方括号;解构赋值从对象挑字段。
...浅拷贝/合并;structuredClone深拷贝。- 对象方法用普通函数,
this才正确。 - 原型链实现继承,所有对象最终都连到 Object.prototype。
下一篇我们看 JS 的数组——和对象并列的常用数据结构。
← 上一篇 JavaScript 作用域与闭包
下一篇 JavaScript 数组 →