面向对象编程

本章将详细介绍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的面向对象编程更加直观和易于理解,但它仍然是基于原型的继承。理解原型链的工作原理有助于更好地使用类。在实际开发中,合理使用继承和组合可以创建更加灵活和可维护的代码结构。