面向对象编程
本章将详细介绍ECMAScript 6中引入的类语法和面向对象编程概念,包括类的定义、继承、封装、静态方法等,帮助您掌握现代JavaScript的面向对象编程方式。
15.1 面向对象编程概述
面向对象编程(OOP)是一种编程范式,它使用对象来组织代码。JavaScript通过原型继承实现面向对象编程,ES6引入了类语法使OOP更加直观。
面向对象的核心概念
- 封装:将数据和操作数据的方法组合在一起,隐藏内部实现细节
- 继承:子类可以继承父类的属性和方法,实现代码复用
- 多态:同一接口可以有不同的实现方式
- 抽象:提取事物的本质特征,忽略非关键细节
15.2 类的定义
ES6引入了类语法,使JavaScript的面向对象编程更加清晰。
基本类定义
// 定义一个简单的类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `你好, 我是${this.name}`;
}
getInfo() {
return `${this.name}今年${this.age}岁`;
}
}
// 创建类的实例
let person = new Person("张三", 25);
console.log(person.greet()); // "你好, 我是张三"
console.log(person.getInfo()); // "张三今年25岁"
类表达式
// 类表达式
let Person = class {
constructor(name) {
this.name = name;
}
greet() {
return `你好, 我是${this.name}`;
}
};
// 命名类表达式
let Person = class PersonClass {
constructor(name) {
this.name = name;
}
greet() {
// 在类内部可以使用PersonClass引用类
return `你好, 我是${this.name}`;
}
};
15.3 构造函数和属性
构造函数用于初始化对象实例,属性用于存储对象的状态。
构造函数
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
getPerimeter() {
return 2 * (this.width + this.height);
}
}
let rect = new Rectangle(10, 5);
console.log(rect.getArea()); // 50
console.log(rect.getPerimeter()); // 30
实例属性和方法
class Counter {
// 实例属性(ES2022)
count = 0;
increment() {
this.count++;
return this.count;
}
decrement() {
this.count--;
return this.count;
}
reset() {
this.count = 0;
return this.count;
}
}
let counter = new Counter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
15.4 方法类型
类中可以定义不同类型的方法。
原型方法
class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
multiply(a, b) {
return a * b;
}
divide(a, b) {
if (b === 0) {
throw new Error("除数不能为零");
}
return a / b;
}
}
let calc = new Calculator();
console.log(calc.add(5, 3)); // 8
console.log(calc.multiply(4, 6)); // 24
Getter和Setter
class User {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// Getter
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
// Setter
set fullName(name) {
let parts = name.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
}
get initials() {
return `${this.firstName.charAt(0)}${this.lastName.charAt(0)}`;
}
}
let user = new User("张", "三");
console.log(user.fullName); // "张 三"
console.log(user.initials); // "张三"
user.fullName = "李 四";
console.log(user.firstName); // "李"
console.log(user.lastName); // "四"
静态方法
class MathUtils {
// 静态方法
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
static PI = 3.14159; // 静态属性(ES2022)
// 静态方法可以访问静态属性
static getCircleArea(radius) {
return this.PI * radius * radius;
}
}
// 调用静态方法不需要创建实例
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.getCircleArea(5)); // 78.53975
15.5 类的继承
继承允许子类获得父类的属性和方法。
基本继承
// 父类
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} 发出声音`);
}
}
// 子类
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
speak() {
super.speak(); // 调用父类方法
console.log(`${this.name} 汪汪叫`);
}
wagTail() {
console.log(`${this.name} 摇尾巴`);
}
}
let dog = new Dog("旺财", "金毛");
dog.speak(); // "旺财 发出声音" 和 "旺财 汪汪叫"
dog.wagTail(); // "旺财 摇尾巴"
继承链
class Animal {
constructor(name) {
this.name = name;
}
}
class Mammal extends Animal {
constructor(name, hasFur) {
super(name);
this.hasFur = hasFur;
}
}
class Dog extends Mammal {
constructor(name, breed) {
super(name, true); // 哺乳动物有毛发
this.breed = breed;
}
bark() {
console.log(`${this.name} 汪汪叫`);
}
}
let dog = new Dog("旺财", "金毛");
console.log(dog.name); // "旺财"
console.log(dog.hasFur); // true
dog.bark(); // "旺财 汪汪叫"
15.6 私有属性和方法
ES2022引入了私有属性和方法,使用#前缀定义。
class BankAccount {
// 私有属性
#balance = 0;
#accountNumber;
constructor(accountNumber, initialBalance = 0) {
this.#accountNumber = accountNumber;
this.#balance = initialBalance;
}
// 公共方法
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return this.#balance;
}
throw new Error("存款金额必须大于0");
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
return this.#balance;
}
throw new Error("取款金额无效");
}
getBalance() {
return this.#balance;
}
// 私有方法
#validateAmount(amount) {
return typeof amount === 'number' && amount > 0;
}
// 私有getter
get #maskedAccountNumber() {
return "****" + this.#accountNumber.slice(-4);
}
getAccountInfo() {
return `账户: ${this.#maskedAccountNumber}, 余额: $${this.#balance}`;
}
}
let account = new BankAccount("1234567890", 1000);
console.log(account.deposit(500)); // 1500
console.log(account.withdraw(200)); // 1300
console.log(account.getBalance()); // 1300
console.log(account.getAccountInfo()); // "账户: ****7890, 余额: $1300"
// 以下代码会报错:
// console.log(account.#balance); // SyntaxError
// account.#balance = 10000; // SyntaxError
15.7 类的静态块
ES2022引入了静态块,用于在类定义时执行初始化代码。
class DatabaseConnection {
static #instance = null;
static #config = null;
// 静态块
static {
try {
// 模拟加载配置
this.#config = {
host: "localhost",
port: 5432,
database: "myapp"
};
console.log("数据库配置加载成功");
} catch (error) {
console.error("配置加载失败:", error);
}
}
static getInstance() {
if (!this.#instance) {
this.#instance = new DatabaseConnection();
}
return this.#instance;
}
getConfig() {
return DatabaseConnection.#config;
}
}
let db1 = DatabaseConnection.getInstance();
let db2 = DatabaseConnection.getInstance();
console.log(db1 === db2); // true (单例模式)
15.8 类字段声明
ES2022允许在类中直接声明字段,无需在构造函数中初始化。
class Product {
// 公共字段
name;
price = 0;
// 私有字段
#category = "未分类";
#sku;
constructor(name, price, category, sku) {
this.name = name;
this.price = price;
this.#category = category;
this.#sku = sku;
}
// 公共字段可以有初始值
discount = 0.1;
// 静态字段
static #nextId = 1;
static idPrefix = "PRD";
// 实例字段
id = `${Product.idPrefix}-${Product.#nextId++}`;
getInfo() {
return {
id: this.id,
name: this.name,
price: this.price,
category: this.#category,
finalPrice: this.price * (1 - this.discount)
};
}
}
let product1 = new Product("笔记本电脑", 5000, "电子产品", "LAPTOP001");
let product2 = new Product("鼠标", 100, "电子产品", "MOUSE001");
console.log(product1.getInfo());
console.log(product2.getInfo());
15.9 Mixins模式
虽然JavaScript不直接支持多重继承,但可以通过Mixins实现类似功能。
// 可飞行的mixin
let Flyable = {
fly() {
console.log(`${this.name} 正在飞行`);
},
land() {
console.log(`${this.name} 着陆了`);
}
};
// 可游泳的mixin
let Swimmable = {
swim() {
console.log(`${this.name} 正在游泳`);
},
dive() {
console.log(`${this.name} 潜水了`);
}
};
// 工具函数:将mixin应用到类
function applyMixins(targetClass, ...mixins) {
mixins.forEach(mixin => {
Object.getOwnPropertyNames(mixin).forEach(name => {
if (name !== 'constructor') {
targetClass.prototype[name] = mixin[name];
}
});
});
}
// 基础类
class Animal {
constructor(name) {
this.name = name;
}
}
class Bird extends Animal {
constructor(name) {
super(name);
}
}
class Fish extends Animal {
constructor(name) {
super(name);
}
}
// 应用mixins
applyMixins(Bird, Flyable);
applyMixins(Fish, Swimmable);
let eagle = new Bird("老鹰");
eagle.fly(); // "老鹰 正在飞行"
eagle.land(); // "老鹰 着陆了"
let shark = new Fish("鲨鱼");
shark.swim(); // "鲨鱼 正在游泳"
shark.dive(); // "鲨鱼 潜水了"
15.10 实际应用示例
用户管理系统
class User {
// 私有字段
#id;
#email;
#password;
// 静态字段
static #userCount = 0;
constructor(name, email, password) {
this.name = name;
this.#email = email;
this.#password = this.#hashPassword(password);
this.#id = ++User.#userCount;
this.createdAt = new Date();
}
// 私有方法
#hashPassword(password) {
// 简单的哈希模拟(实际应用中应使用加密库)
return btoa(password);
}
// Getter
get id() {
return this.#id;
}
get email() {
return this.#email;
}
// 静态方法
static validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
// 公共方法
updatePassword(oldPassword, newPassword) {
if (this.#hashPassword(oldPassword) === this.#password) {
this.#password = this.#hashPassword(newPassword);
return true;
}
return false;
}
getUserInfo() {
return {
id: this.#id,
name: this.name,
email: this.#email,
createdAt: this.createdAt
};
}
}
// 管理员类继承用户类
class Admin extends User {
constructor(name, email, password) {
super(name, email, password);
this.role = "admin";
}
deleteUser(userId) {
console.log(`管理员 ${this.name} 删除了用户 ${userId}`);
}
getUserInfo() {
const info = super.getUserInfo();
return {
...info,
role: this.role
};
}
}
// 使用示例
let user = new User("张三", "zhangsan@example.com", "password123");
let admin = new Admin("李四", "lisi@example.com", "admin123");
console.log(user.getUserInfo());
console.log(admin.getUserInfo());
admin.deleteUser(123);
游戏角色系统
class Character {
// 实例字段
name;
level = 1;
health = 100;
maxHealth = 100;
attack = 10;
defense = 5;
constructor(name) {
this.name = name;
}
takeDamage(damage) {
const actualDamage = Math.max(0, damage - this.defense);
this.health = Math.max(0, this.health - actualDamage);
console.log(`${this.name} 受到 ${actualDamage} 点伤害,剩余生命值: ${this.health}`);
return this.health;
}
heal(amount) {
this.health = Math.min(this.maxHealth, this.health + amount);
console.log(`${this.name} 恢复了 ${amount} 点生命值`);
}
isAlive() {
return this.health > 0;
}
levelUp() {
this.level++;
this.maxHealth += 20;
this.health = this.maxHealth;
this.attack += 5;
this.defense += 2;
console.log(`${this.name} 升级到 ${this.level} 级!`);
}
}
class Warrior extends Character {
constructor(name) {
super(name);
this.attack += 10;
this.defense += 5;
}
// 战士的特殊技能
powerAttack() {
const damage = this.attack * 2;
console.log(`${this.name} 使用强力攻击,造成 ${damage} 点伤害!`);
return damage;
}
}
class Mage extends Character {
constructor(name) {
super(name);
this.mana = 100;
this.maxMana = 100;
this.magicAttack = 20;
}
// 法师的特殊技能
fireball() {
if (this.mana >= 20) {
this.mana -= 20;
const damage = this.magicAttack;
console.log(`${this.name} 施放火球术,造成 ${damage} 点魔法伤害!`);
return damage;
} else {
console.log(`${this.name} 魔力不足!`);
return 0;
}
}
meditate() {
const manaRestored = 30;
this.mana = Math.min(this.maxMana, this.mana + manaRestored);
console.log(`${this.name} 冥想恢复了 ${manaRestored} 点魔力`);
}
}
// 使用示例
let warrior = new Warrior("战士");
let mage = new Mage("法师");
warrior.levelUp();
mage.levelUp();
let damage1 = warrior.powerAttack();
mage.takeDamage(damage1);
let damage2 = mage.fireball();
warrior.takeDamage(damage2);
mage.meditate();
提示:ES6的类语法使JavaScript的面向对象编程更加直观和易于理解,但它仍然是基于原型的继承。理解原型链的工作原理有助于更好地使用类。在实际开发中,合理使用继承和组合可以创建更加灵活和可维护的代码结构。