一、前言
在 JavaScript 中,对象是核心数据结构,几乎所有复杂功能的实现都离不开对象。与传统面向对象语言(如 Java)不同,JS 没有类(ES6 之前)的概念,而是通过工厂模式、构造函数模式、原型模式等灵活机制创建对象,并基于原型链实现属性与方法的共享。
二、对象的本质与核心概念
2.1 什么是 JavaScript 对象?
- 定义:对象是 “键值对” 的无序集合,键(属性名)为字符串或 Symbol,值(属性值)可以是基本类型(
string/number)、引用类型(数组 / 函数)或其他对象。 - 本质:可以理解为 “散列表”,用于存储相关联的数据与功能(方法),例如:
javascript
运行
// 简单对象示例 var person = { name: "张三", // 基本类型属性 age: 18, sayName: function() { // 方法(函数作为属性值) console.log("我的名字是:" + this.name); }, hobbies: ["篮球", "编程"] // 引用类型属性 }; - 核心特性:
- 动态性:可随时添加 / 删除属性(如
person.gender = "男")。 - 原型继承:每个对象都有原型(
__proto__),可继承原型上的属性与方法。 - 属性特性:支持控制属性是否可读写、可枚举、可删除(通过
Object.defineProperty())。
- 动态性:可随时添加 / 删除属性(如
三、对象的创建模式:从简单到高级
JS 提供多种创建对象的方式,各有优劣,需根据场景选择。
3.1 工厂模式:批量创建对象的 “流水线”
3.1.1 核心思想
通过函数封装对象创建过程,接收参数动态设置属性与方法,批量生成结构相同的对象,解决 “重复创建相似对象” 的问题。
3.1.2 代码实现与运行结果
javascript
运行
// 工厂函数:接收参数,返回对象
function createPerson(name, age, gender) {
// 1. 创建空对象
var person = new Object();
// 2. 动态添加属性与方法
person.name = name;
person.age = age;
person.gender = gender;
person.sayName = function() {
console.log("姓名:" + this.name + ",年龄:" + this.age);
};
// 3. 返回创建的对象
return person;
}
// 批量创建对象
var person1 = createPerson("张三", 18, "男");
var person2 = createPerson("李四", 20, "女");
// 测试
person1.sayName(); // 调用方法
person2.sayName();
console.log("person1 类型:", typeof person1); // 查看对象类型
运行结果:
plaintext
姓名:张三,年龄:18
姓名:李四,年龄:20
person1 类型: object
3.1.3 优缺点
- 优点:简单高效,适合批量创建结构一致的对象。
- 缺点:无法区分对象类型(所有对象均为
Object类型),无法通过instanceof判断对象归属(如无法区分 “人” 和 “动物” 对象)。
3.2 构造函数模式:可识别类型的 “蓝图”
3.2.1 核心思想
通过自定义构造函数(首字母大写,约定俗成),结合 new 关键字创建对象,将属性与方法绑定到 this(指向新对象),解决工厂模式 “无法区分类型” 的问题。
3.2.2 代码实现与运行结果
javascript
运行
// 构造函数(首字母大写,标识为构造器)
function Person(name, age, gender) {
// this 指向新创建的对象
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function() {
console.log("姓名:" + this.name);
};
}
// 使用 new 关键字创建实例
var person1 = new Person("张三", 18, "男");
var person2 = new Person("李四", 20, "女");
// 测试
person1.sayName(); // 调用方法
console.log("person1 是 Person 实例吗?", person1 instanceof Person); // 类型判断
console.log("person1 是 Object 实例吗?", person1 instanceof Object); // 所有对象继承自 Object
console.log("person1 与 person2 方法是否相同?", person1.sayName === person2.sayName); // 方法独立性
运行结果:
plaintext
姓名:张三
person1 是 Person 实例吗? true
person1 是 Object 实例吗? true
person1 与 person2 方法是否相同? false
3.2.3 new 关键字的执行过程
- 在内存中创建一个新的空对象。
- 新对象的
[[Prototype]]属性(隐式原型)指向构造函数的prototype属性(显式原型)。 - 构造函数内部的
this指向新对象。 - 执行构造函数内部代码(给新对象添加属性与方法)。
- 如果构造函数没有返回非空对象,则返回新创建的对象。
3.2.4 优缺点
- 优点:可通过
instanceof判断对象类型,语义清晰,适合创建同一 “类” 的对象。 - 缺点:每个实例的方法都是独立函数(
person1.sayName !== person2.sayName),造成内存浪费(相同逻辑的方法被重复创建)。
3.3 原型模式:共享方法的 “优化方案”
3.3.1 核心思想
利用原型链特性,将对象的共享方法定义在构造函数的 prototype(显式原型)上,所有实例通过原型链共享这些方法,解决构造函数模式 “方法重复创建” 的问题。
3.3.2 代码实现与运行结果
javascript
运行
// 构造函数:仅初始化实例属性(每个实例独立)
function Person() {}
// 原型对象:定义共享属性与方法(所有实例共享)
Person.prototype.name = "默认姓名";
Person.prototype.age = 18;
Person.prototype.sayName = function() {
console.log("姓名:" + this.name);
};
// 创建实例
var person1 = new Person();
var person2 = new Person();
// 测试 1:共享方法
console.log("person1 与 person2 方法是否相同?", person1.sayName === person2.sayName); // 方法共享
// 测试 2:实例属性覆盖原型属性
person1.name = "张三"; // 实例属性覆盖原型属性
console.log("person1 姓名:", person1.name); // 读取实例属性
console.log("person2 姓名:", person2.name); // 读取原型属性
// 测试 3:原型链查找
console.log("person1 有 sayName 实例属性吗?", person1.hasOwnProperty("sayName")); // false(方法在原型上)
console.log("sayName 在 person1 的原型上吗?", "sayName" in person1); // true(原型链查找)
运行结果:
plaintext
person1 与 person2 方法是否相同? true
person1 姓名: 张三
person2 姓名: 默认姓名
person1 有 sayName 实例属性吗? false
sayName 在 person1 的原型上吗? true
3.3.3 原型链查找规则
- 访问对象属性时,先查找实例自身的属性。
- 若实例无该属性,则查找原型对象(
constructor.prototype)。 - 若原型对象无该属性,则继续向上查找原型链(直到
Object.prototype)。 - 若最终未找到,返回
undefined。
3.3.4 优缺点
- 优点:方法共享,节省内存;支持原型链继承,可扩展对象功能。
- 缺点:原型上的引用类型属性(如数组)会被所有实例共享,易造成数据污染(如一个实例修改数组,所有实例受影响)。
3.4 组合模式(推荐):兼顾性能与灵活性
3.4.1 核心思想
结合构造函数模式(定义实例属性,确保独立性)和原型模式(定义共享方法,节省内存),是 JS 中最常用的对象创建模式。
3.4.2 代码实现与运行结果
javascript
运行
// 构造函数:定义实例属性(独立属性,含引用类型)
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.friends = ["王五", "赵六"]; // 引用类型属性(每个实例独立)
}
// 原型模式:定义共享方法
Person.prototype.sayName = function() {
console.log("姓名:" + this.name);
};
Person.prototype.showFriends = function() {
console.log(this.name + " 的朋友:" + this.friends);
};
// 创建实例
var person1 = new Person("张三", 18, "男");
var person2 = new Person("李四", 20, "女");
// 测试 1:引用类型属性独立性
person1.friends.push("孙七"); // 仅修改 person1 的 friends
person1.showFriends();
person2.showFriends();
// 测试 2:方法共享
console.log("person1 与 person2 方法是否相同?", person1.sayName === person2.sayName);
// 测试 3:类型判断
console.log("person1 是 Person 实例吗?", person1 instanceof Person);
运行结果:
plaintext
张三 的朋友:王五,赵六,孙七
李四 的朋友:王五,赵六
person1 与 person2 方法是否相同? true
person1 是 Person 实例吗? true
3.4.3 核心优势
- 实例属性独立:引用类型属性(如数组)定义在构造函数中,避免数据污染。
- 方法共享:方法定义在原型上,所有实例复用同一函数,节省内存。
- 类型可识别:支持
instanceof判断,语义清晰,符合面向对象思想。
四、对象的高级特性:属性控制与序列化
4.1 数据属性与访问器属性:精细化控制属性
JS 中对象的属性分为两类:数据属性(存储值)和访问器属性(通过 get/set 方法控制访问),可通过 Object.defineProperty() 自定义属性特性(如是否可读写、可枚举)。
4.1.1 数据属性特性
| 特性名 | 描述 | 默认值(直接定义属性时) |
|---|---|---|
configurable | 是否可通过 delete 删除属性、修改特性或转为访问器属性 | true |
enumerable | 是否可通过 for...in 循环枚举 | true |
writable | 是否可修改属性值 | true |
value | 属性的实际值 | undefined |
4.1.2 访问器属性特性
| 特性名 | 描述 | 默认值 |
|---|---|---|
configurable | 是否可删除或修改特性 | false |
enumerable | 是否可通过 for...in 循环枚举 | false |
get | 读取属性时调用的函数,返回属性值 | undefined |
set | 修改属性时调用的函数,接收新值作为参数 | undefined |
4.1.3 代码实现与运行结果
javascript
运行
// 1. 定义数据属性:控制属性可读写性
var person = {};
Object.defineProperty(person, "name", {
value: "张三",
writable: false, // 不可修改
enumerable: true, // 可枚举
configurable: true // 可删除
});
console.log("修改前 name:", person.name);
person.name = "李四"; // 尝试修改(writable: false,修改无效)
console.log("修改后 name:", person.name);
// 2. 定义访问器属性:监听属性变化(模拟数据双向绑定)
var user = {
_age: 18 // 下划线表示“私有”属性,建议通过访问器访问
};
Object.defineProperty(user, "age", {
get: function() {
console.log("读取 age 属性");
return this._age;
},
set: function(newAge) {
console.log("修改 age 属性:" + newAge);
if (newAge >= 0 && newAge <= 150) { // 校验新值
this._age = newAge;
} else {
console.log("年龄无效!");
}
}
});
// 测试访问器属性
console.log("user.age:", user.age); // 触发 get 方法
user.age = 20; // 触发 set 方法(有效)
user.age = 200; // 触发 set 方法(无效)
console.log("修改后 user.age:", user.age);
运行结果:
plaintext
修改前 name: 张三
修改后 name: 张三
读取 age 属性
user.age: 18
修改 age 属性:20
修改 age 属性:200
年龄无效!
读取 age 属性
修改后 user.age: 20
4.2 对象序列化:JSON 转换与深拷贝
对象序列化是将对象状态转换为字符串(JSON.stringify()),反序列化是将字符串还原为对象(JSON.parse()),常用于数据存储、接口传输与深拷贝。
4.2.1 核心规则
- 可序列化类型:
string、number、boolean、array、普通对象(不含函数、Symbol)。 - 不可序列化类型:
function、Symbol、undefined、RegExp、Error、Date(会被转为 ISO 字符串)。
4.2.2 代码实现与运行结果
javascript
运行
// 1. 对象序列化(转为 JSON 字符串)
var person = {
name: "张三",
age: 18,
hobbies: ["篮球", "编程"],
birthday: new Date("2005-01-01")
};
var jsonStr = JSON.stringify(person);
console.log("序列化结果:", jsonStr);
console.log("序列化结果类型:", typeof jsonStr);
// 2. 对象反序列化(JSON 字符串转为对象)
var parsedPerson = JSON.parse(jsonStr);
console.log("反序列化结果:", parsedPerson);
console.log("反序列化后 birthday 类型:", typeof parsedPerson.birthday); // Date 被转为字符串
// 3. 深拷贝(解决引用类型浅拷贝问题)
var obj1 = { a: 1, b: { c: 2 } };
var obj2 = JSON.parse(JSON.stringify(obj1)); // 深拷贝
obj2.b.c = 3; // 修改 obj2 的引用属性
console.log("obj1.b.c:", obj1.b.c); // 原对象不受影响
console.log("obj2.b.c:", obj2.b.c);
运行结果:
plaintext
序列化结果: {"name":"张三","age":18,"hobbies":["篮球","编程"],"birthday":"2004-12-31T16:00:00.000Z"}
序列化结果类型: string
反序列化结果: {
name: '张三',
age: 18,
hobbies: [ '篮球', '编程' ],
birthday: '2004-12-31T16:00:00.000Z'
}
反序列化后 birthday 类型: string
obj1.b.c: 2
obj2.b.c: 3
4.3 原型链与继承:对象间的 “传承”
4.3.1 原型链基础
- 原型链:实例的
__proto__→ 构造函数的prototype→ 父构造函数的prototype→ ... →Object.prototype(原型链顶端)。 - 继承本质:通过原型链让子对象访问父对象的属性与方法,是 JS 实现继承的核心机制。
4.3.2 代码实现与运行结果
javascript
运行
// 父构造函数
function Animal(name) {
this.name = name;
}
// 父原型方法
Animal.prototype.sayName = function() {
console.log("动物名称:" + this.name);
};
// 子构造函数:通过原型链继承 Animal
function Dog(name) {
Animal.call(this, name); // 盗用构造函数,继承实例属性
}
Dog.prototype = new Animal(); // 原型链继承,继承原型方法
Dog.prototype.constructor = Dog; // 修复 constructor 指向(否则指向 Animal)
// 子原型方法:扩展功能
Dog.prototype.sayWang = function() {
console.log("汪汪汪!");
};
// 测试继承
var dog = new Dog("小黑");
dog.sayName(); // 继承父原型方法
dog.sayWang(); // 子原型方法
console.log("dog 是 Dog 实例吗?", dog instanceof Dog);
console.log("dog 是 Animal 实例吗?", dog instanceof Animal);
运行结果:
plaintext
动物名称:小黑
汪汪汪!
dog 是 Dog 实例吗? true
dog 是 Animal 实例吗? true
五、实战案例:封装一个 “学生信息管理对象”
基于组合模式与原型链,实现一个支持 “添加学生、查询学生、修改成绩” 的管理对象,展示对象高级特性的实战应用。
5.1 代码实现
javascript
运行
// 1. 学生构造函数(实例属性)
function Student(name, studentId, score) {
this.name = name;
this.studentId = studentId;
this.score = score; // 成绩:{ math: 0, chinese: 0, english: 0 }
}
// 2. 学生原型方法(共享方法)
Student.prototype.calcAverage = function() {
// 计算平均分
var total = this.score.math + this.score.chinese + this.score.english;
return (total / 3).toFixed(2);
};
Student.prototype.showInfo = function() {
console.log(`学号:${this.studentId},姓名:${this.name},平均分:${this.calcAverage()}`);
};
// 3. 管理对象(单例模式,无需实例化)
var StudentManager = {
students: [], // 存储学生实例
// 添加学生
addStudent: function(student) {
if (student instanceof Student) {
this.students.push(student);
console.log(`成功添加学生:${student.name}`);
} else {
console.error("添加失败:必须传入 Student 实例");
}
},
// 根据学号查询学生
findStudentById: function(studentId) {
return this.students.find(student => student.studentId === studentId);
},
// 修改学生成绩
updateScore: function(studentId, subject, newScore) {
var student = this.findStudentById(studentId);
if (student && student.score[subject]) {
student.score[subject] = newScore;
console.log(`成功修改 ${student.name} 的 ${subject} 成绩为 ${newScore}`);
} else {
console.error("修改失败:学生不存在或科目无效");
}
}
};
// 4. 测试
// 添加学生
var student1 = new Student("张三", "2025001", { math: 90, chinese: 85, english: 95 });
var student2 = new Student("李四", "2025002", { math: 88, chinese: 92, english: 80 });
StudentManager.addStudent(student1);
StudentManager.addStudent(student2);
// 查询并展示学生信息
var foundStudent = StudentManager.findStudentById("2025001");
foundStudent.showInfo();
// 修改成绩
StudentManager.updateScore("2025002", "math", 95);
var updatedStudent = StudentManager.findStudentById("2025002");
updatedStudent.showInfo();
5.2 运行结果
plaintext
成功添加学生:张三
成功添加学生:李四
学号:2025001,姓名:张三,平均分:90.00
成功修改 李四 的 math 成绩为 95
学号:2025002,姓名:李四,平均分:92.33
六、总结与开发建议
对象创建模式选择:
- 简单对象(无复用需求):用对象字面量(
var obj = {})。 - 批量创建相似对象:用组合模式(构造函数 + 原型),兼顾独立性与性能。
- 单例对象(如管理类):用对象字面量直接定义(
var Manager = {})。
- 简单对象(无复用需求):用对象字面量(
属性控制要点:
- 需保护的属性(如 “年龄”):用访问器属性(
get/set)添加校验逻辑。 - 不可修改的常量:用
Object.defineProperty()设置writable: false。 - 避免枚举的属性:设置
enumerable: false(如内部方法)。
- 需保护的属性(如 “年龄”):用访问器属性(
序列化与继承避坑:
- 序列化时注意不可序列化类型(如函数、
Symbol),需提前过滤。 - 继承时修复
constructor指向,避免类型判断错误。 - 原型上避免定义引用类型属性,防止数据污染。
- 序列化时注意不可序列化类型(如函数、
浙公网安备 33010602011771号