JavaScript 对象
对象是存储键值对的容器,是 JavaScript 的核心概念之一。
对象基础
创建对象
// 字面量方式(最常用)
const person = {
name: "张三",
age: 20,
isStudent: true
};
// 构造函数方式
const person2 = new Object();
// Object.create()
const person3 = Object.create(Object.prototype);
person3.name = "李四";
person3.age = 25;
访问对象属性
const person = {
name: "张三",
age: 20,
address: {
city: "北京",
district: "朝阳区"
}
};
// 点符号访问
console.log(person.name); // 张三
console.log(person.age); // 20
// 方括号访问
console.log(person["name"]); // 张三
console.log(person["age"]); // 20
// 访问嵌套属性
console.log(person.address.city); // 北京
console.log(person["address"]["district"]); // 朝阳区
// 动态访问
const key = "name";
console.log(person[key]); // 张三
添加、修改和删除属性
const person = {
name: "张三",
age: 20
};
// 添加属性
person.city = "北京";
person["country"] = "中国";
// 修改属性
person.age = 21;
// 删除属性
delete person.city;
// 检查属性是否存在
console.log("name" in person); // true
console.log("city" in person); // false
console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("toString")); // false
对象属性特性
属性描述符
const person = { name: "张三" };
// 获取属性描述符
console.log(Object.getOwnPropertyDescriptor(person, "name"));
// {
// value: "张三",
// writable: true,
// enumerable: true,
// configurable: true
// }
// 定义单个属性
Object.defineProperty(person, "age", {
value: 20,
writable: false, // 不可修改
enumerable: true, // 可枚举
configurable: true // 可配置
});
console.log(Object.getOwnPropertyDescriptors(person));
属性标志
const user = {};
// writable - 是否可修改值
Object.defineProperty(user, "name", {
value: "张三",
writable: false
});
// user.name = "李四"; // 严格模式下报错
// enumerable - 是否可枚举
Object.defineProperty(user, "age", {
value: 20,
enumerable: false
});
console.log(Object.keys(user)); // ["name"](age 不可枚举)
// configurable - 是否可删除或修改特性
Object.defineProperty(user, "city", {
value: "北京",
configurable: false
});
// delete user.city; // 严格模式下报错
访问器属性
const person = {
_name: "张三", // 私有属性(约定以下划线开头)
// getter
get name() {
return this._name;
},
// setter
set name(value) {
if (value.length < 2) {
throw new Error("名字长度至少2个字符");
}
this._name = value;
}
};
console.log(person.name); // 张三(调用 getter)
person.name = "李四"; // 调用 setter
console.log(person.name); // 李四
// 使用 Object.defineProperty 定义访问器
const temperature = {
_celsius: 0
};
Object.defineProperty(temperature, "fahrenheit", {
get() {
return this._celsius * 9/5 + 32;
},
set(value) {
this._celsius = (value - 32) * 5/9;
}
});
console.log(temperature.fahrenheit); // 32
temperature.fahrenheit = 68;
console.log(temperature._celsius); // 20
对象方法
Object.keys、values、entries
const person = {
name: "张三",
age: 20,
city: "北京"
};
// 获取所有键
console.log(Object.keys(person)); // ["name", "age", "city"]
// 获取所有值
console.log(Object.values(person)); // ["张三", 20, "北京"]
// 获取所有键值对
console.log(Object.entries(person)); // [["name", "张三"], ["age", 20], ["city", "北京"]]
// 遍历对象
for (const [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
// 转换为 Map
const map = new Map(Object.entries(person));
console.log(map.get("name")); // 张三
Object.assign
合并对象:
const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };
const result = Object.assign(target, source);
console.log(result); // { a: 1, b: 3, c: 4 }
console.log(target); // { a: 1, b: 3, c: 4 }(修改了 target)
// 浅拷贝
const obj = { x: 1, y: { z: 2 } };
const copy = Object.assign({}, obj);
Object.freeze 和 Object.seal
const person = { name: "张三", age: 20 };
// Object.freeze - 冻结对象(不能添加、删除、修改属性)
Object.freeze(person);
person.name = "李四"; // 静默失败或严格模式报错
person.city = "北京"; // 静默失败或严格模式报错
delete person.name; // 静默失败或严格模式报错
console.log(person); // { name: "张三", age: 20 }
// Object.seal - 密封对象(不能添加、删除属性,但可以修改)
const user = { name: "张三", age: 20 };
Object.seal(user);
user.age = 21; // 可以修改
user.city = "北京"; // 静默失败
delete user.name; // 静默失败
console.log(user); // { name: "张三", age: 21 }
// 检查状态
console.log(Object.isFrozen(person)); // true
console.log(Object.isSealed(user)); // true
Object.create
// 使用 Object.create 创建具有特定原型的对象
const personProto = {
greet() {
console.log(`你好,我叫${this.name}`);
}
};
const person = Object.create(personProto);
person.name = "张三";
person.greet(); // 你好,我叫张三
// 创建没有原型的对象
const pureObj = Object.create(null);
pureObj.name = "张三";
console.log(pureObj); // { name: "张三" }(没有 Object.prototype 上的方法)
对象的拷贝
浅拷贝
const original = { a: 1, b: { c: 2 } };
// 展开运算符
const copy1 = { ...original };
// Object.assign
const copy2 = Object.assign({}, original);
// Array.from
const copy3 = Object.fromEntries(Object.entries(original));
// 注意:都是浅拷贝
copy1.b.c = 100;
console.log(original.b.c); // 100(被修改了)
深拷贝
const original = {
a: 1,
b: {
c: 2,
d: [1, 2, 3]
},
e: new Date()
};
// JSON 方法(不能拷贝函数、undefined、Date 对象等)
const copy1 = JSON.parse(JSON.stringify(original));
copy1.b.c = 100;
console.log(original.b.c); // 2(未被修改)
// 递归深拷贝函数
function deepClone(obj, hash = new WeakMap()) {
// 处理 null 和非对象类型
if (obj === null || typeof obj !== "object") {
return obj;
}
// 处理 Date
if (obj instanceof Date) {
return new Date(obj);
}
// 处理数组
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item, hash));
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理普通对象
const clone = {};
hash.set(obj, clone);
for (const key of Object.keys(obj)) {
clone[key] = deepClone(obj[key], hash);
}
return clone;
}
const copy2 = deepClone(original);
copy2.b.c = 100;
console.log(original.b.c); // 2(未被修改)
解构赋值
对象解构
const person = { name: "张三", age: 20, city: "北京" };
// 基本解构
const { name, age } = person;
console.log(name, age); // 张三 20
// 重命名变量
const { name: userName, age: userAge } = person;
console.log(userName, userAge); // 张三 20
// 设置默认值
const { name, country = "中国" } = person;
console.log(country); // 中国
// 嵌套解构
const { address: { city, district } } = { address: { city: "北京", district: "朝阳" } };
console.log(city, district); // 北京 朝阳
数组解构
const colors = ["红", "绿", "蓝"];
// 基本解构
const [first, second, third] = colors;
console.log(first, second, third); // 红 绿 蓝
// 跳过元素
const [primary, , tertiary] = colors;
console.log(primary, tertiary); // 红 蓝
// 剩余元素
const [first, ...rest] = colors;
console.log(first, rest); // 红 ["绿", "蓝"]
// 默认值
const [a = 1, b = 2] = [10];
console.log(a, b); // 10 2
// 交换变量
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1
函数参数解构
// 对象参数解构
function greet({ name, age = 18 }) {
console.log(`你好,我叫${name},今年${age}岁`);
}
greet({ name: "张三", age: 20 }); // 你好,我叫张三,今年20岁
greet({ name: "李四" }); // 你好,我叫李四,今年18岁
// 数组参数解构
function getScore([first, second, ...others]) {
console.log(`第一名:${first}`);
console.log(`第二名:${second}`);
console.log(`其他:${others.join(", ")}`);
}
getScore([95, 88, 76, 92]);
this 关键字
函数调用中的 this
const person = {
name: "张三",
// 普通函数 - this 指向调用对象
greet: function() {
console.log(`你好,我叫${this.name}`);
},
// 箭头函数 - this 指向定义时的上下文
sayHi: () => {
console.log(`你好,我叫${this.name}`);
}
};
person.greet(); // 你好,我叫张三(this 指向 person)
person.sayHi(); // 你好,我叫undefined(this 指向全局对象)
改变 this 指向
const person1 = { name: "张三" };
const person2 = { name: "李四" };
function greet(greeting) {
console.log(`${greeting},我叫${this.name}`);
}
greet.call(person1, "你好"); // 你好,我叫张三
greet.apply(person2, ["你好"]); // 你好,我叫李四
const boundGreet = greet.bind(person1);
boundGreet("你好"); // 你好,我叫张三
对象的继承
原型链继承
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name}正在吃东西`);
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数
this.breed = breed;
}
// 建立原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(`${this.name}汪汪叫`);
};
const dog = new Dog("旺财", "金毛");
dog.eat(); // 旺财正在吃东西
dog.bark(); // 旺财汪汪叫
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
ES6 类继承
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name}正在吃东西`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
bark() {
console.log(`${this.name}汪汪叫`);
}
}
const dog = new Dog("旺财", "金毛");
dog.eat(); // 旺财正在吃东西
dog.bark(); // 旺财汪汪叫
小结
本章我们学习了:
- 对象的创建方法
- 访问、添加、修改和删除对象属性
- 对象属性特性(writable、enumerable、configurable)
- 访问器属性(getter 和 setter)
- Object 常用方法(keys、values、entries、assign、freeze)
- 对象的浅拷贝和深拷贝
- 解构赋值
- this 关键字
- 对象的继承(原型链和 ES6 类)
练习
- 创建一个学生对象,包含姓名、学号、多门课程成绩
- 实现一个银行账户对象,包含存钱、取钱、查询余额功能
- 使用解构从嵌套对象中提取数据
- 实现一个浅拷贝函数
shallowClone(obj) - 使用原型链实现一个 Stack 类(push、pop、peek、isEmpty)