跳到主要内容

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(); // 旺财汪汪叫

小结

本章我们学习了:

  1. 对象的创建方法
  2. 访问、添加、修改和删除对象属性
  3. 对象属性特性(writable、enumerable、configurable)
  4. 访问器属性(getter 和 setter)
  5. Object 常用方法(keys、values、entries、assign、freeze)
  6. 对象的浅拷贝和深拷贝
  7. 解构赋值
  8. this 关键字
  9. 对象的继承(原型链和 ES6 类)

练习

  1. 创建一个学生对象,包含姓名、学号、多门课程成绩
  2. 实现一个银行账户对象,包含存钱、取钱、查询余额功能
  3. 使用解构从嵌套对象中提取数据
  4. 实现一个浅拷贝函数 shallowClone(obj)
  5. 使用原型链实现一个 Stack 类(push、pop、peek、isEmpty)