面向对象编程(Object-Oriented Programming,OOP)是一种重要的技术,有助于编写清晰可靠的可以重复使用的代码。
1. 面向对象编程
1.1 概述
面向对象编程(Object-Oriented Programming, OOP) 是一种编程范式,旨在通过“对象”来组织代码逻辑。与 面向过程编程(Procedural Programming) 关注执行步骤不同,OOP 将数据(属性)和操作数据的方法(行为)封装在对象中,通过对象之间的交互构建系统。其核心目标是提高代码的重用性、可维护性和扩展性。
1.2 核心概念:类与实例
1.2.1 类 (Class)
类是对一组具有相同属性和行为的事物的抽象定义。它规定了对象的结构和功能,属于一种逻辑上的定义。
1.2.2 实例 (Instance) / 对象 (Object)
实例是根据类创建的具体实体。实例占有实际的内存空间,具备类所定义的具体属性值和可执行的方法。
// 定义类
class Person {
constructor(name, age) {
this.name = name; // 属性
this.age = age;
}
sayHello() { // 方法
console.log(`My name is ${this.name}`);
}
}
// 实例化对象
const instance = new Person("Alice", 30);
instance.sayHello();
1.3 面向对象编程的四大支柱
封装
将数据(属性)和操作数据的方法绑定在一起,并限制对组件内部数据的直接访问。
-
目的:保护内部状态,防止外部代码直接修改内部数据导致不一致,仅对外暴露必要的接口。
-
实现:通过类的方法操作内部属性,或使用私有字段(
#field)限制访问。
继承
允许一个类(子类)获取另一个类(父类)的属性和方法。
-
目的:实现代码复用,建立类与类之间的层级关系。
-
实现:使用
extends关键字。
多态
指不同类的对象对同一消息(方法调用)做出不同的响应。
-
目的:提高接口的通用性,解耦合。
-
实现:子类重写(Override)父类的方法,使得同一方法名在不同对象实例中表现出不同的逻辑。
抽象
隐藏复杂的实现细节,只展示核心功能接口。
- 目的:降低系统复杂度,使用户只需关注如何使用接口,而无需了解底层实现。
1.4 JavaScript OOP 的底层机制
1.4.1 语言本质
JavaScript 本质上是 基于原型(Prototype-based) 的语言,而非传统的基于类(Class-based)的语言(如 Java 或 C++)。
1.4.2 class 关键字的实质
ES6 引入的 class 关键字是语法糖。它并没有改变 JavaScript 基于原型的继承机制,只是提供了一种更清晰、更符合传统 OOP 习惯的写法。
- 类型验证:
typeof ClassName的返回值为'function',证明类的底层依然是函数。
1.4.3 构造函数与对象模板
在 ES6 之前,JavaScript 使用构造函数(Constructor Function)作为“对象模板”来定义类。
2. 创建对象
2.1 创建直接实例 (Object Literals)
在现代 JavaScript 中,创建单个对象最直接、最标准的方式是使用对象字面量 (Object Literal),而不是使用 new Object()。
核心语法
-
使用花括号
{}定义对象边界。 -
内部包含键值对(属性和方法)。
-
使用
const声明对象变量,保证引用不被意外修改。
// 现代标准写法
const myNewObject = {
// 属性 (Property)
info: 'I am a shiny new object',
// 方法 (Method) - 使用 ES6 方法简写语法
showInfo() {
// 在对象方法内部,this 指向当前对象实例
console.log(this.info);
}
};
// 调用方法
myNewObject.showInfo(); // 输出: "I am a shiny new object"
注意:将函数赋值给对象时,不要加括号(如
myNewObject.showInfo是引用函数,myNewObject.showInfo()是执行函数)。
2.2 使用关键字 this
this 是一个动态指针,它的值取决于函数的调用环境,而不是定义环境。
-
作为方法调用:当函数挂载在对象上并被调用时(如
obj.method()),this指向该对象。 -
独立调用与严格模式:
-
现代开发默认开启严格模式 ('use strict')(ES6 模块和 Class 中自动开启)。
-
在严格模式下,独立调用函数(如
myFunc()),this的值为undefined。这避免了旧版本中this意外指向全局对象 (window) 导致污染全局变量的问题。
-
const user = {
name: "Alice",
greet() {
console.log("Hello, " + this.name);
}
};
user.greet(); // this 指向 user,输出: "Hello, Alice"
const extractedFunc = user.greet;
extractedFunc(); // 报错或无输出 (严格模式下 this 为 undefined)
2.3 使用类 (Class) 代替构造函数
当需要创建多个结构相同的对象(如多个用户、多个按钮)时,现代 JavaScript 使用 class 关键字来定义模板,而不是使用普通的函数。
-
class关键字:定义类的名称(通常首字母大写)。 -
constructor方法:类的构造函数,用于初始化对象的属性(数据)。 -
类体中的方法:定义对象行为。重要:写在类体中的方法会自动挂载到原型(Prototype)上,所有实例共享同一份内存,性能极佳。
// ✅ 现代标准:使用 class 定义模板
class Person {
// 构造函数:初始化数据
constructor(name) {
this.name = name;
this.info = `I am called ${this.name}`;
}
// 方法:定义行为 (自动共享内存)
showInfo() {
console.log(this.info);
}
}
2.4 对象实例化 (Instantiation)
定义好 class 后,使用 new 关键字来创建具体的实例(Instance)。
-
new关键字会在内存中创建一个新对象。 -
自动调用
constructor方法,初始化属性(如this.name = 'Adam')。 -
将新对象与类的原型连接,使其可以使用
showInfo等方法。
// 创建实例
const person1 = new Person('Adam');
const person2 = new Person('Eve');
// 调用各自的方法
person1.showInfo(); // 输出: I am called Adam
person2.showInfo(); // 输出: I am called Eve
// 验证方法共享 (性能优化验证)
console.log(person1.showInfo === person2.showInfo); // true
2.5 构造函数参数与解构赋值
在实例化对象时,我们通常需要传递参数来定制对象。
2.5.1 基础参数传递
直接在 constructor 中接收参数。
class Book {
constructor(title, author) {
this.title = title;
this.author = author;
}
}
const myBook = new Book("JavaScript Guide", "MDN");
2.5.2 高级参数处理:对象解构 (最佳实践)
当参数较多(超过3个)时,按顺序传递参数容易出错(如记不清是先传“年份”还是先传“颜色”)。现代开发推荐传入一个配置对象。
class Car {
// ✅ 接收一个对象,直接解构出需要的属性,并设置默认值
constructor({ color, make, year, mileage = 0 }) {
this.color = color;
this.make = make;
this.year = year;
this.mileage = mileage;
}
drive(distance) {
this.mileage += distance;
console.log(`New mileage: ${this.mileage}`);
}
}
// 实例化时,无需记忆参数顺序,代码可读性极高
const myCar = new Car({
make: "Ford",
year: 1998,
color: "Blue",
mileage: 79500 // 甚至可以省略,使用默认值
});
3. 继承
“将子类 prototype 指向父类实例”的做法(Dog.prototype = new Pet())已完全被废弃。现代 JavaScript 使用 extends 关键字实现继承,语法更清晰,且避免了副作用。
-
extends:声明一个类继承自另一个类(子类继承父类)。 -
super():在子类的构造函数 (constructor) 中,必须在使用this之前调用super()。它负责调用父类的构造函数,完成基础属性的初始化。
父类:Pet
定义通用的属性和方法。
class Pet {
constructor() {
this.animal = "";
this.name = "";
}
setAnimal(newAnimal) {
this.animal = newAnimal;
}
setName(newName) {
this.name = newName;
}
}
子类:Dog
继承 Pet 的所有功能,并添加特有的属性 breed (品种)。
class Dog extends Pet {
constructor() {
// 关键:调用父类构造函数,初始化 animal 和 name
super();
// 子类特有的属性初始化
this.breed = "";
}
// 子类特有的方法
setBreed(newBreed) {
this.breed = newBreed;
}
// 演示:子类也可以重写(Override)父类方法
setName(newName) {
super.setName(newName); // 调用父类逻辑
console.log(`Dog name updated to: ${this.name}`);
}
}
// 使用继承
const myDog = new Dog();
// 1. 使用父类的方法
myDog.setAnimal("Canine");
myDog.setName("Alan");
// 2. 使用子类的方法
myDog.setBreed("Greyhound");
// 3. 验证数据
console.log(`${myDog.name} is a ${myDog.breed}`); // 输出: Alan is a Greyhound