JavaScript 对象
对象是 JavaScript 中的核心概念,它是一种复合数据类型,可以存储多个键值对。对象在 JavaScript 中无处不在,几乎所有的东西都是对象。
🎯 对象的重要性
对象是 JavaScript 编程的基础,它允许我们将相关的数据和功能组织在一起。掌握对象的使用方法是成为一名优秀 JavaScript 开发者的关键。
什么是对象?
对象是一种包含键值对的复合数据类型,其中键是字符串(或 Symbol),值可以是任何数据类型,包括函数。
创建对象的方法
1. 对象字面量
// 使用对象字面量创建对象
const person = {
name: "张三",
age: 25,
job: "工程师",
greet: function() {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
}
};
// 访问对象属性
console.log(person.name); // 输出: 张三
console.log(person["age"]); // 输出: 25
// 调用对象方法
console.log(person.greet()); // 输出: 你好,我是 张三,今年 25 岁。
2. Object 构造函数
// 使用 Object 构造函数创建对象
const person = new Object();
// 添加属性
person.name = "李四";
person.age = 30;
person.job = "设计师";
// 添加方法
person.greet = function() {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
};
// 访问对象属性和方法
console.log(person.name); // 输出: 李四
console.log(person.greet()); // 输出: 你好,我是 李四,今年 30 岁。
3. Object.create() 方法
// 创建原型对象
const personPrototype = {
greet: function() {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
}
};
// 使用 Object.create() 创建对象
const person = Object.create(personPrototype);
// 添加属性
person.name = "王五";
person.age = 35;
person.job = "经理";
// 访问对象属性和方法
console.log(person.name); // 输出: 王五
console.log(person.greet()); // 输出: 你好,我是 王五,今年 35 岁。
4. 构造函数
// 定义构造函数
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.greet = function() {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
};
}
// 使用构造函数创建对象
const person1 = new Person("赵六", 28, "工程师");
const person2 = new Person("钱七", 32, "设计师");
// 访问对象属性和方法
console.log(person1.name); // 输出: 赵六
console.log(person2.greet()); // 输出: 你好,我是 钱七,今年 32 岁。
5. ES6 类
// 定义类
class Person {
// 构造方法
constructor(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
// 实例方法
greet() {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
}
// 静态方法
static createPerson(name, age, job) {
return new Person(name, age, job);
}
}
// 使用类创建对象
const person1 = new Person("孙八", 26, "工程师");
const person2 = Person.createPerson("周九", 30, "设计师");
// 访问对象属性和方法
console.log(person1.name); // 输出: 孙八
console.log(person2.greet()); // 输出: 你好,我是 周九,今年 30 岁。
对象属性
1. 访问属性
const person = {
name: "张三",
age: 25,
job: "工程师"
};
// 点表示法
console.log(person.name); // 输出: 张三
// 方括号表示法
console.log(person["age"]); // 输出: 25
// 使用变量访问属性
const propName = "job";
console.log(person[propName]); // 输出: 工程师
2. 添加和修改属性
const person = {
name: "张三",
age: 25
};
// 添加新属性
person.job = "工程师";
person["salary"] = 10000;
// 修改现有属性
person.age = 26;
person["name"] = "李四";
console.log(person); // 输出: { name: "李四", age: 26, job: "工程师", salary: 10000 }
3. 删除属性
const person = {
name: "张三",
age: 25,
job: "工程师"
};
// 删除属性
delete person.job;
console.log(person); // 输出: { name: "张三", age: 25 }
// 检查属性是否存在
console.log("job" in person); // 输出: false
console.log("name" in person); // 输出: true
// 检查属性是否是对象自身的
console.log(person.hasOwnProperty("name")); // 输出: true
console.log(person.hasOwnProperty("toString")); // 输出: false(toString 是继承的方法)
对象方法
对象方法是存储在对象属性中的函数:
const calculator = {
// 方法
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
},
multiply: function(a, b) {
return a * b;
},
divide: function(a, b) {
if (b === 0) {
return "除数不能为零";
}
return a / b;
},
// ES6 方法简写
power(a, b) {
return Math.pow(a, b);
}
};
// 调用对象方法
console.log(calculator.add(5, 3)); // 输出: 8
console.log(calculator.subtract(10, 4)); // 输出: 6
console.log(calculator.multiply(6, 7)); // 输出: 42
console.log(calculator.divide(15, 3)); // 输出: 5
console.log(calculator.power(2, 3)); // 输出: 8
this 关键字
在对象方法中,this 指向调用该方法的对象:
const person = {
name: "张三",
age: 25,
greet: function() {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
}
};
console.log(person.greet()); // 输出: 你好,我是 张三,今年 25 岁。
// 注意:箭头函数中的 this 指向定义时的上下文
const person2 = {
name: "李四",
age: 30,
greet: () => {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
}
};
console.log(person2.greet()); // 输出: 你好,我是 undefined,今年 undefined 岁。
对象遍历
1. for...in 循环
const person = {
name: "张三",
age: 25,
job: "工程师"
};
// 使用 for...in 循环遍历对象
for (const key in person) {
if (person.hasOwnProperty(key)) {
console.log(`${key}: ${person[key]}`);
}
}
// 输出:
// name: 张三
// age: 25
// job: 工程师
2. Object.keys() 方法
const person = {
name: "张三",
age: 25,
job: "工程师"
};
// 使用 Object.keys() 获取对象的键
const keys = Object.keys(person);
console.log(keys); // 输出: ["name", "age", "job"]
// 遍历键
keys.forEach(key => {
console.log(`${key}: ${person[key]}`);
});
// 输出:
// name: 张三
// age: 25
// job: 工程师
3. Object.values() 方法
const person = {
name: "张三",
age: 25,
job: "工程师"
};
// 使用 Object.values() 获取对象的值
const values = Object.values(person);
console.log(values); // 输出: ["张三", 25, "工程师"]
4. Object.entries() 方法
const person = {
name: "张三",
age: 25,
job: "工程师"
};
// 使用 Object.entries() 获取对象的键值对
const entries = Object.entries(person);
console.log(entries); // 输出: [["name", "张三"], ["age", 25], ["job", "工程师"]]
// 遍历键值对
entries.forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// 输出:
// name: 张三
// age: 25
// job: 工程师
对象继承
1. 原型继承
// 父对象
const person = {
name: "张三",
age: 25,
greet: function() {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
}
};
// 子对象,继承自 person
const employee = Object.create(person);
employee.job = "工程师";
employee.salary = 10000;
employee.name = "李四"; // 覆盖继承的属性
// 添加新方法
employee.work = function() {
return `${this.name} 正在工作。`;
};
// 访问继承的属性和方法
console.log(employee.greet()); // 输出: 你好,我是 李四,今年 25 岁。
console.log(employee.work()); // 输出: 李四 正在工作。
2. 构造函数继承
// 父构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 添加原型方法
Person.prototype.greet = function() {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
};
// 子构造函数
function Employee(name, age, job, salary) {
// 调用父构造函数
Person.call(this, name, age);
this.job = job;
this.salary = salary;
}
// 设置原型继承
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
// 添加子构造函数的方法
Employee.prototype.work = function() {
return `${this.name} 正在工作,职位是 ${this.job}。`;
};
// 创建实例
const employee = new Employee("王五", 30, "工程师", 12000);
// 访问属性和方法
console.log(employee.name); // 输出: 王五
console.log(employee.greet()); // 输出: 你好,我是 王五,今年 30 岁。
console.log(employee.work()); // 输出: 王五 正在工作,职位是 工程师。
3. ES6 类继承
// 父类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
}
}
// 子类
class Employee extends Person {
constructor(name, age, job, salary) {
// 调用父类构造函数
super(name, age);
this.job = job;
this.salary = salary;
}
work() {
return `${this.name} 正在工作,职位是 ${this.job}。`;
}
}
// 创建实例
const employee = new Employee("赵六", 28, "设计师", 10000);
// 访问属性和方法
console.log(employee.name); // 输出: 赵六
console.log(employee.greet()); // 输出: 你好,我是 赵六,今年 28 岁。
console.log(employee.work()); // 输出: 赵六 正在工作,职位是 设计师。
对象操作方法
// 1. Object.assign():合并对象
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const obj3 = { d: 5 };
const mergedObj = Object.assign({}, obj1, obj2, obj3);
console.log(mergedObj); // 输出: { a: 1, b: 3, c: 4, d: 5 }
// 2. Object.freeze():冻结对象(不能添加、删除或修改属性)
const frozenObj = { a: 1, b: 2 };
Object.freeze(frozenObj);
frozenObj.a = 10; // 无效果
console.log(frozenObj); // 输出: { a: 1, b: 2 }
// 3. Object.seal():密封对象(不能添加或删除属性,但可以修改现有属性)
const sealedObj = { a: 1, b: 2 };
Object.seal(sealedObj);
sealedObj.a = 10; // 可以修改
sealedObj.c = 3; // 无效果
delete sealedObj.b; // 无效果
console.log(sealedObj); // 输出: { a: 10, b: 2 }
// 4. Object.is():比较两个值是否严格相等
console.log(Object.is(5, 5)); // 输出: true
console.log(Object.is(NaN, NaN)); // 输出: true
console.log(Object.is(0, -0)); // 输出: false
// 5. Object.keys():返回对象的自有可枚举属性的键
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj)); // 输出: ["a", "b", "c"]
// 6. Object.values():返回对象的自有可枚举属性的值
console.log(Object.values(obj)); // 输出: [1, 2, 3]
// 7. Object.entries():返回对象的自有可枚举属性的键值对
console.log(Object.entries(obj)); // 输出: [["a", 1], ["b", 2], ["c", 3]]
// 8. Object.fromEntries():将键值对数组转换为对象
const entries = [["a", 1], ["b", 2], ["c", 3]];
const newObj = Object.fromEntries(entries);
console.log(newObj); // 输出: { a: 1, b: 2, c: 3 }
对象最佳实践
- 使用对象字面量创建对象:对象字面量语法简洁明了,是创建对象的首选方法
- 使用 ES6 方法简写:对于对象方法,使用简写语法使代码更简洁
- 使用 const 声明对象:除非需要重新赋值对象引用,否则使用 const 声明
- 避免使用全局对象:全局对象容易导致命名冲突,应尽量避免
- 使用对象解构:使用解构赋值简化对象属性的访问
- 使用 Object.assign() 或扩展运算符合并对象:这两种方法都可以创建新对象,避免修改原对象
- 使用 ES6 类:对于需要创建多个相似对象的情况,使用类可以提高代码的可维护性
- 注意 this 的绑定:在使用对象方法时,注意 this 的指向问题
- 使用 Object.keys()、Object.values() 和 Object.entries() 遍历对象:这些方法提供了更灵活的对象遍历方式
- 考虑使用 Map 替代对象:当键不是字符串时,Map 可能是更好的选择
对象常见问题
- 问题 1: this 指向错误
- 问题 2: 对象引用问题
- 问题 3: 原型链污染
- 问题 4: 属性枚举问题
- 问题 5: 继承实现复杂
解决方案:了解 this 的绑定规则,在需要时使用 bind()、call() 或 apply() 方法,或使用箭头函数。
解决方案:了解对象是引用类型,当需要复制对象时,使用 Object.assign() 或扩展运算符创建浅拷贝,或使用 JSON.parse(JSON.stringify()) 创建深拷贝。
解决方案:避免修改 Object.prototype 等内置对象的原型,使用 Object.create(null) 创建没有原型的对象。
解决方案:了解可枚举属性和不可枚举属性的区别,使用 Object.getOwnPropertyNames() 获取所有自有属性(包括不可枚举的)。
解决方案:使用 ES6 类和 extends 关键字,这使继承实现更加简洁明了。
📝 学习检查
通过本章节的学习,你应该了解:
- 对象的基本概念和创建方法
- 对象属性的访问、添加、修改和删除
- 对象方法的定义和调用
- this 关键字在对象中的使用
- 对象的遍历方法
- 对象继承的实现方式
- 常用的对象操作方法
- 对象的最佳实践和常见问题