Skip to content

JavaScript类与面向对象编程

JavaScript虽然是一种基于原型的语言,但ES6引入的class语法为面向对象编程提供了更直观的方式。本文将深入探讨JavaScript中的类与面向对象编程概念和实践。

面向对象编程基础

什么是面向对象编程

面向对象编程(OOP)是一种编程范式,它将现实世界的概念抽象为对象,强调:

  • 封装:将数据和操作数据的方法组合在一起
  • 继承:子类可以继承父类的属性和方法
  • 多态:同一接口可以有不同的实现
  • 抽象:隐藏复杂的实现细节,只暴露必要的接口

面向对象与函数式编程的对比

面向对象编程关注"对象是什么",而函数式编程关注"做什么":

javascript
// 面向对象风格:计算器类
class Calculator {
  constructor() {
    this.result = 0;
  }
  
  add(value) {
    this.result += value;
    return this;
  }
  
  multiply(value) {
    this.result *= value;
    return this;
  }
  
  getResult() {
    return this.result;
  }
}

const calc = new Calculator();
const result = calc.add(5).multiply(2).getResult(); // 10

// 函数式风格:纯函数组合
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);

const calculate = pipe(
  x => add(x, 5),
  x => multiply(x, 2)
);
const result = calculate(0); // 10

类的基础语法

类声明与类表达式

JavaScript提供了两种定义类的方式:

javascript
// 类声明
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  greet() {
    return `你好,我是${this.name},今年${this.age}岁`;
  }
}

// 类表达式
const Animal = class {
  constructor(species) {
    this.species = species;
  }
  
  makeSound() {
    return `${this.species}发出了声音`;
  }
};

// 命名类表达式
const Vehicle = class Car {
  constructor(brand) {
    this.brand = brand;
  }
  
  start() {
    return `${this.brand}汽车启动了`;
  }
};

类声明与函数声明的区别:

  • 类声明不会被提升(hoisting)
  • 类声明会自动启用严格模式
  • 类的方法不可枚举

构造函数与实例化

构造函数是类的特殊方法,用于创建和初始化对象实例:

javascript
class User {
  constructor(username, email) {
    // 实例属性
    this.username = username;
    this.email = email;
    this.createdAt = new Date();
    this.isActive = true;
    
    // 参数验证
    if (!username || !email) {
      throw new Error('用户名和邮箱不能为空');
    }
    
    // 初始化逻辑
    this.id = this.generateId();
  }
  
  generateId() {
    return Math.random().toString(36).substr(2, 9);
  }
  
  getProfile() {
    return {
      id: this.id,
      username: this.username,
      email: this.email,
      createdAt: this.createdAt,
      isActive: this.isActive
    };
  }
}

// 创建实例
const user1 = new User('张三', 'zhangsan@example.com');
const user2 = new User('李四', 'lisi@example.com');

console.log(user1.getProfile());
console.log(user2.getProfile());

实例方法与原型

类中定义的方法会被添加到类的原型上:

javascript
class Counter {
  constructor(initialValue = 0) {
    this.value = initialValue;
  }
  
  // 实例方法
  increment() {
    this.value++;
    return this;
  }
  
  decrement() {
    this.value--;
    return this;
  }
  
  reset() {
    this.value = 0;
    return this;
  }
  
  getValue() {
    return this.value;
  }
}

const counter = new Counter(5);

// 方法链调用
counter.increment().increment().decrement();
console.log(counter.getValue()); // 6

// 验证方法在原型上
console.log(Counter.prototype.increment); // [Function: increment]
console.log(counter.hasOwnProperty('increment')); // false
console.log(counter.hasOwnProperty('value')); // true

类的高级特性

静态方法与静态属性

静态成员属于类本身,而不是类的实例:

javascript
class MathUtils {
  // 静态属性
  static PI = 3.14159;
  static E = 2.71828;
  
  // 静态方法
  static add(a, b) {
    return a + b;
  }
  
  static multiply(a, b) {
    return a * b;
  }
  
  static circleArea(radius) {
    return this.PI * radius * radius;
  }
  
  static factorial(n) {
    if (n <= 1) return 1;
    return n * this.factorial(n - 1);
  }
}

// 使用静态成员
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.circleArea(5)); // 78.53975
console.log(MathUtils.factorial(5)); // 120

// 静态方法不能通过实例调用
const math = new MathUtils();
// console.log(math.add(1, 2)); // TypeError: math.add is not a function

实际应用场景:

javascript
class User {
  constructor(username, email) {
    this.username = username;
    this.email = email;
    this.id = User.generateId();
  }
  
  // 静态方法:工厂方法
  static createAdmin(username, email) {
    const admin = new User(username, email);
    admin.role = 'admin';
    admin.permissions = ['read', 'write', 'delete'];
    return admin;
  }
  
  static createGuest() {
    const guest = new User('guest', 'guest@example.com');
    guest.role = 'guest';
    guest.permissions = ['read'];
    return guest;
  }
  
  // 静态方法:工具方法
  static generateId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
  }
  
  static validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
  
  // 静态方法:查找方法
  static findById(users, id) {
    return users.find(user => user.id === id);
  }
}

// 使用工厂方法
const admin = User.createAdmin('admin', 'admin@example.com');
const guest = User.createGuest();

console.log(admin.role); // 'admin'
console.log(guest.permissions); // ['read']

// 使用工具方法
console.log(User.validateEmail('test@example.com')); // true
console.log(User.validateEmail('invalid-email')); // false

访问器属性(Getter/Setter)

访问器属性允许我们定义看起来像属性但实际上是方法的成员:

javascript
class Temperature {
  constructor(celsius = 0) {
    this._celsius = celsius;
  }
  
  // Getter:获取摄氏度
  get celsius() {
    return this._celsius;
  }
  
  // Setter:设置摄氏度
  set celsius(value) {
    if (typeof value !== 'number') {
      throw new Error('温度必须是数字');
    }
    if (value < -273.15) {
      throw new Error('温度不能低于绝对零度');
    }
    this._celsius = value;
  }
  
  // Getter:获取华氏度
  get fahrenheit() {
    return this._celsius * 9/5 + 32;
  }
  
  // Setter:设置华氏度
  set fahrenheit(value) {
    this.celsius = (value - 32) * 5/9;
  }
  
  // Getter:获取开尔文温度
  get kelvin() {
    return this._celsius + 273.15;
  }
  
  // Setter:设置开尔文温度
  set kelvin(value) {
    this.celsius = value - 273.15;
  }
  
  toString() {
    return `${this._celsius}°C (${this.fahrenheit}°F, ${this.kelvin}K)`;
  }
}

const temp = new Temperature(25);
console.log(temp.toString()); // "25°C (77°F, 298.15K)"

// 通过不同单位设置温度
temp.fahrenheit = 100;
console.log(temp.celsius); // 37.77777777777778

temp.kelvin = 300;
console.log(temp.celsius); // 26.850000000000023

访问器的实际应用:

javascript
class BankAccount {
  constructor(accountNumber, initialBalance = 0) {
    this.accountNumber = accountNumber;
    this._balance = initialBalance;
    this._transactions = [];
  }
  
  // 只读属性
  get balance() {
    return this._balance;
  }
  
  get transactions() {
    return [...this._transactions]; // 返回副本,防止外部修改
  }
  
  // 计算属性
  get totalDeposits() {
    return this._transactions
      .filter(t => t.type === 'deposit')
      .reduce((sum, t) => sum + t.amount, 0);
  }
  
  get totalWithdrawals() {
    return this._transactions
      .filter(t => t.type === 'withdrawal')
      .reduce((sum, t) => sum + t.amount, 0);
  }
  
  deposit(amount) {
    if (amount <= 0) {
      throw new Error('存款金额必须大于0');
    }
    this._balance += amount;
    this._transactions.push({
      type: 'deposit',
      amount,
      date: new Date(),
      balance: this._balance
    });
  }
  
  withdraw(amount) {
    if (amount <= 0) {
      throw new Error('取款金额必须大于0');
    }
    if (amount > this._balance) {
      throw new Error('余额不足');
    }
    this._balance -= amount;
    this._transactions.push({
      type: 'withdrawal',
      amount,
      date: new Date(),
      balance: this._balance
    });
  }
}

const account = new BankAccount('123456789', 1000);
account.deposit(500);
account.withdraw(200);

console.log(account.balance); // 1300
console.log(account.totalDeposits); // 500
console.log(account.totalWithdrawals); // 200

私有字段和方法

现代浏览器支持真正的私有字段和方法,使用#前缀:

javascript
class SecureWallet {
  // 私有字段
  #balance = 0;
  #pin;
  #attempts = 0;
  #maxAttempts = 3;
  #locked = false;

  constructor(initialBalance, pin) {
    this.#balance = initialBalance;
    this.#pin = pin;
  }

  // 私有方法
  #validatePin(inputPin) {
    if (this.#locked) {
      throw new Error('钱包已被锁定');
    }

    if (inputPin !== this.#pin) {
      this.#attempts++;
      if (this.#attempts >= this.#maxAttempts) {
        this.#locked = true;
        throw new Error('钱包已被锁定,尝试次数过多');
      }
      throw new Error(`PIN错误,还有${this.#maxAttempts - this.#attempts}次机会`);
    }

    this.#attempts = 0; // 重置尝试次数
  }

  #log(action, amount) {
    console.log(`[${new Date().toISOString()}] ${action}: ${amount}, 余额: ${this.#balance}`);
  }

  // 公有方法
  getBalance(pin) {
    this.#validatePin(pin);
    return this.#balance;
  }

  deposit(amount, pin) {
    this.#validatePin(pin);
    if (amount <= 0) {
      throw new Error('存款金额必须大于0');
    }
    this.#balance += amount;
    this.#log('存款', amount);
  }

  withdraw(amount, pin) {
    this.#validatePin(pin);
    if (amount <= 0) {
      throw new Error('取款金额必须大于0');
    }
    if (amount > this.#balance) {
      throw new Error('余额不足');
    }
    this.#balance -= amount;
    this.#log('取款', amount);
  }

  changePin(oldPin, newPin) {
    this.#validatePin(oldPin);
    this.#pin = newPin;
    console.log('PIN已更改');
  }

  unlock(masterKey) {
    if (masterKey === 'MASTER_RESET_2024') {
      this.#locked = false;
      this.#attempts = 0;
      console.log('钱包已解锁');
    } else {
      throw new Error('主密钥错误');
    }
  }
}

const wallet = new SecureWallet(1000, '1234');

// 正常操作
wallet.deposit(500, '1234');
console.log(wallet.getBalance('1234')); // 1500

// 私有字段无法从外部访问
// console.log(wallet.#balance); // SyntaxError

try {
  wallet.getBalance('0000');
} catch (error) {
  console.log(error.message);
}

私有字段的特点:

  • 只能在类内部访问
  • 不会被继承
  • 不能动态添加
  • 提供真正的封装

类字段声明

现代JavaScript允许直接在类体中声明字段:

javascript
class GameCharacter {
  // 公有字段
  name = '未命名角色';
  level = 1;
  experience = 0;

  // 私有字段
  #health = 100;
  #maxHealth = 100;
  #mana = 50;
  #maxMana = 50;

  // 静态字段
  static maxLevel = 100;
  static experienceTable = [0, 100, 300, 600, 1000];

  constructor(name, characterClass = 'warrior') {
    this.name = name;
    this.characterClass = characterClass;
    this.skills = this.#getInitialSkills(characterClass);
  }

  #getInitialSkills(characterClass) {
    const skillSets = {
      warrior: ['剑术', '盾牌防御'],
      mage: ['火球术', '治疗术'],
      archer: ['精准射击', '快速移动']
    };
    return skillSets[characterClass] || skillSets.warrior;
  }

  // Getter for health percentage
  get healthPercentage() {
    return (this.#health / this.#maxHealth) * 100;
  }

  get isAlive() {
    return this.#health > 0;
  }

  takeDamage(damage) {
    this.#health = Math.max(0, this.#health - damage);
    console.log(`${this.name}受到${damage}点伤害,剩余生命值:${this.#health}`);

    if (!this.isAlive) {
      console.log(`${this.name}已死亡`);
    }
  }

  heal(amount) {
    this.#health = Math.min(this.#maxHealth, this.#health + amount);
    console.log(`${this.name}恢复${amount}点生命值,当前生命值:${this.#health}`);
  }

  gainExperience(exp) {
    this.experience += exp;
    console.log(`${this.name}获得${exp}点经验值`);

    // 检查是否升级
    while (this.level < GameCharacter.maxLevel &&
           this.experience >= GameCharacter.experienceTable[this.level]) {
      this.levelUp();
    }
  }

  levelUp() {
    this.level++;
    this.#maxHealth += 20;
    this.#health = this.#maxHealth; // 升级时恢复满血
    this.#maxMana += 10;
    this.#mana = this.#maxMana;

    console.log(`🎉 ${this.name}升级到${this.level}级!`);
  }

  getStatus() {
    return {
      name: this.name,
      class: this.characterClass,
      level: this.level,
      experience: this.experience,
      health: `${this.#health}/${this.#maxHealth}`,
      mana: `${this.#mana}/${this.#maxMana}`,
      skills: this.skills,
      isAlive: this.isAlive
    };
  }
}

const hero = new GameCharacter('亚瑟', 'warrior');
console.log(hero.getStatus());

hero.gainExperience(150);
hero.takeDamage(30);
hero.heal(10);

私有字段注意事项:

  • 需要现代浏览器支持(Chrome 74+, Firefox 90+)
  • Node.js 12+ 支持
  • 老版本浏览器需要Babel转译

## 继承与多态

### 基础继承

使用`extends`关键字实现类的继承:

```javascript
// 基类
class Animal {
  constructor(name, species) {
    this.name = name;
    this.species = species;
    this.energy = 100;
  }

  eat(food) {
    this.energy += 20;
    console.log(`${this.name}吃了${food},能量恢复到${this.energy}`);
  }

  sleep() {
    this.energy = 100;
    console.log(`${this.name}睡了一觉,精力充沛`);
  }

  makeSound() {
    console.log(`${this.name}发出了声音`);
  }

  getInfo() {
    return `${this.name}是一只${this.species},当前能量:${this.energy}`;
  }
}

// 子类:狗
class Dog extends Animal {
  constructor(name, breed) {
    super(name, '狗'); // 调用父类构造函数
    this.breed = breed;
    this.loyalty = 100;
  }

  // 重写父类方法
  makeSound() {
    console.log(`${this.name}汪汪叫`);
  }

  // 新增方法
  fetch() {
    if (this.energy < 20) {
      console.log(`${this.name}太累了,需要休息`);
      return;
    }
    this.energy -= 20;
    console.log(`${this.name}去捡球了,能量降到${this.energy}`);
  }

  wagTail() {
    console.log(`${this.name}开心地摇尾巴`);
  }

  // 重写父类方法,添加额外逻辑
  getInfo() {
    return `${super.getInfo()},品种:${this.breed},忠诚度:${this.loyalty}`;
  }
}

// 子类:猫
class Cat extends Animal {
  constructor(name, color) {
    super(name, '猫');
    this.color = color;
    this.independence = 80;
  }

  makeSound() {
    console.log(`${this.name}喵喵叫`);
  }

  climb() {
    if (this.energy < 15) {
      console.log(`${this.name}太累了,不想爬树`);
      return;
    }
    this.energy -= 15;
    console.log(`${this.name}爬到了树上,能量降到${this.energy}`);
  }

  purr() {
    console.log(`${this.name}满足地呼噜呼噜`);
  }

  getInfo() {
    return `${super.getInfo()},颜色:${this.color},独立性:${this.independence}`;
  }
}

// 使用示例
const dog = new Dog('旺财', '金毛');
const cat = new Cat('咪咪', '橘色');

// 多态示例
const animals = [dog, cat];
animals.forEach(animal => {
  animal.makeSound();
  console.log(animal.getInfo());
});

dog.fetch();
dog.wagTail();

cat.climb();
cat.purr();

抽象类模拟

JavaScript没有内置的抽象类,但可以通过约定和错误抛出来模拟:

javascript
// 抽象基类
class Shape {
  constructor(color) {
    if (new.target === Shape) {
      throw new Error('Shape是抽象类,不能直接实例化');
    }
    this.color = color;
  }

  // 抽象方法:子类必须实现
  getArea() {
    throw new Error('getArea方法必须在子类中实现');
  }

  getPerimeter() {
    throw new Error('getPerimeter方法必须在子类中实现');
  }

  // 具体方法:子类可以直接使用
  getColor() {
    return this.color;
  }

  setColor(color) {
    this.color = color;
  }

  describe() {
    return `这是一个${this.color}的图形,面积为${this.getArea()},周长为${this.getPerimeter()}`;
  }
}

// 具体子类:圆形
class Circle extends Shape {
  constructor(radius, color) {
    super(color);
    this.radius = radius;
  }

  getArea() {
    return Math.PI * this.radius * this.radius;
  }

  getPerimeter() {
    return 2 * Math.PI * this.radius;
  }
}

// 具体子类:矩形
class Rectangle extends Shape {
  constructor(width, height, color) {
    super(color);
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }

  getPerimeter() {
    return 2 * (this.width + this.height);
  }
}

// 使用示例
const circle = new Circle(5, '红色');
const rectangle = new Rectangle(4, 6, '蓝色');

console.log(circle.describe());
console.log(rectangle.describe());

// 直接实例化抽象类会报错
// const shape = new Shape('绿色'); // Error

接口模拟

JavaScript可以通过混入(Mixin)模式模拟接口:

javascript
// 定义接口(混入)
const Flyable = {
  fly() {
    console.log(`${this.name}在飞行`);
  },

  land() {
    console.log(`${this.name}降落了`);
  }
};

const Swimmable = {
  swim() {
    console.log(`${this.name}在游泳`);
  },

  dive() {
    console.log(`${this.name}潜水了`);
  }
};

const Walkable = {
  walk() {
    console.log(`${this.name}在走路`);
  },

  run() {
    console.log(`${this.name}在跑步`);
  }
};

// 混入函数
function mixin(target, ...sources) {
  sources.forEach(source => {
    Object.getOwnPropertyNames(source).forEach(name => {
      if (name !== 'constructor') {
        target.prototype[name] = source[name];
      }
    });
  });
}

// 基类
class Animal {
  constructor(name) {
    this.name = name;
  }
}

// 鸟类:可以飞行和走路
class Bird extends Animal {
  constructor(name, species) {
    super(name);
    this.species = species;
  }
}
mixin(Bird, Flyable, Walkable);

// 鱼类:可以游泳
class Fish extends Animal {
  constructor(name, species) {
    super(name);
    this.species = species;
  }
}
mixin(Fish, Swimmable);

// 鸭子:可以飞行、游泳和走路
class Duck extends Animal {
  constructor(name) {
    super(name);
    this.species = '鸭子';
  }
}
mixin(Duck, Flyable, Swimmable, Walkable);

// 使用示例
const eagle = new Bird('老鹰', '猛禽');
const salmon = new Fish('三文鱼', '鲑鱼');
const duck = new Duck('唐老鸭');

eagle.fly();
eagle.walk();

salmon.swim();
salmon.dive();

duck.fly();
duck.swim();
duck.walk();

设计模式与类

单例模式

确保一个类只有一个实例:

javascript
class DatabaseConnection {
  constructor() {
    if (DatabaseConnection.instance) {
      return DatabaseConnection.instance;
    }

    this.host = 'localhost';
    this.port = 5432;
    this.database = 'myapp';
    this.connected = false;

    DatabaseConnection.instance = this;
  }

  connect() {
    if (!this.connected) {
      console.log(`连接到数据库 ${this.database}@${this.host}:${this.port}`);
      this.connected = true;
    }
    return this;
  }

  disconnect() {
    if (this.connected) {
      console.log('断开数据库连接');
      this.connected = false;
    }
    return this;
  }

  query(sql) {
    if (!this.connected) {
      throw new Error('数据库未连接');
    }
    console.log(`执行查询: ${sql}`);
    return { rows: [], count: 0 };
  }

  static getInstance() {
    if (!DatabaseConnection.instance) {
      DatabaseConnection.instance = new DatabaseConnection();
    }
    return DatabaseConnection.instance;
  }
}

// 使用示例
const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();
const db3 = DatabaseConnection.getInstance();

console.log(db1 === db2); // true,同一个实例
console.log(db2 === db3); // true

db1.connect().query('SELECT * FROM users');

工厂模式

根据参数创建不同类型的对象:

javascript
// 产品基类
class Vehicle {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }

  start() {
    console.log(`${this.make} ${this.model} 启动了`);
  }

  stop() {
    console.log(`${this.make} ${this.model} 停止了`);
  }
}

// 具体产品类
class Car extends Vehicle {
  constructor(make, model, doors) {
    super(make, model);
    this.doors = doors;
    this.type = 'car';
  }

  openTrunk() {
    console.log('后备箱打开了');
  }
}

class Motorcycle extends Vehicle {
  constructor(make, model, engineSize) {
    super(make, model);
    this.engineSize = engineSize;
    this.type = 'motorcycle';
  }

  wheelie() {
    console.log('摩托车翘起前轮');
  }
}

class Truck extends Vehicle {
  constructor(make, model, capacity) {
    super(make, model);
    this.capacity = capacity;
    this.type = 'truck';
  }

  loadCargo() {
    console.log(`装载货物,容量:${this.capacity}吨`);
  }
}

// 工厂类
class VehicleFactory {
  static createVehicle(type, make, model, extra) {
    switch (type.toLowerCase()) {
      case 'car':
        return new Car(make, model, extra);
      case 'motorcycle':
        return new Motorcycle(make, model, extra);
      case 'truck':
        return new Truck(make, model, extra);
      default:
        throw new Error(`不支持的车辆类型: ${type}`);
    }
  }

  // 注册新的车辆类型
  static registerVehicleType(type, vehicleClass) {
    this.vehicleTypes = this.vehicleTypes || {};
    this.vehicleTypes[type.toLowerCase()] = vehicleClass;
  }

  static createRegisteredVehicle(type, ...args) {
    const VehicleClass = this.vehicleTypes?.[type.toLowerCase()];
    if (!VehicleClass) {
      throw new Error(`未注册的车辆类型: ${type}`);
    }
    return new VehicleClass(...args);
  }
}

// 使用示例
const car = VehicleFactory.createVehicle('car', '丰田', '卡罗拉', 4);
const motorcycle = VehicleFactory.createVehicle('motorcycle', '本田', 'CBR600', 600);
const truck = VehicleFactory.createVehicle('truck', '沃尔沃', 'FH16', 40);

car.start();
car.openTrunk();

motorcycle.start();
motorcycle.wheelie();

truck.start();
truck.loadCargo();

// 注册新类型
class Bicycle extends Vehicle {
  constructor(make, model, gears) {
    super(make, model);
    this.gears = gears;
    this.type = 'bicycle';
  }

  pedal() {
    console.log('开始踩踏板');
  }
}

VehicleFactory.registerVehicleType('bicycle', Bicycle);
const bike = VehicleFactory.createRegisteredVehicle('bicycle', '捷安特', 'ATX', 21);
bike.pedal();

观察者模式

实现对象间的一对多依赖关系:

javascript
// 观察者接口
class Observer {
  update(data) {
    throw new Error('update方法必须在子类中实现');
  }
}

// 主题(被观察者)
class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    if (!(observer instanceof Observer)) {
      throw new Error('观察者必须实现Observer接口');
    }
    this.observers.push(observer);
  }

  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }

  notifyObservers(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

// 具体主题:新闻发布者
class NewsPublisher extends Subject {
  constructor() {
    super();
    this.news = [];
  }

  publishNews(title, content, category) {
    const newsItem = {
      id: Date.now(),
      title,
      content,
      category,
      publishTime: new Date()
    };

    this.news.push(newsItem);
    this.notifyObservers(newsItem);
  }

  getNews() {
    return [...this.news];
  }
}

// 具体观察者:新闻订阅者
class NewsSubscriber extends Observer {
  constructor(name, interests = []) {
    super();
    this.name = name;
    this.interests = interests;
    this.receivedNews = [];
  }

  update(newsItem) {
    // 如果没有指定兴趣或新闻类别在兴趣范围内
    if (this.interests.length === 0 || this.interests.includes(newsItem.category)) {
      this.receivedNews.push(newsItem);
      console.log(`${this.name}收到新闻: ${newsItem.title}`);
    }
  }

  getReceivedNews() {
    return [...this.receivedNews];
  }
}

// 具体观察者:邮件通知服务
class EmailNotificationService extends Observer {
  constructor() {
    super();
    this.sentEmails = [];
  }

  update(newsItem) {
    const email = {
      to: 'subscribers@news.com',
      subject: `新闻快报: ${newsItem.title}`,
      body: newsItem.content,
      sentAt: new Date()
    };

    this.sentEmails.push(email);
    console.log(`邮件通知已发送: ${newsItem.title}`);
  }

  getSentEmails() {
    return [...this.sentEmails];
  }
}

// 使用示例
const publisher = new NewsPublisher();

const subscriber1 = new NewsSubscriber('张三', ['科技', '体育']);
const subscriber2 = new NewsSubscriber('李四', ['财经']);
const subscriber3 = new NewsSubscriber('王五'); // 订阅所有类别
const emailService = new EmailNotificationService();

// 添加观察者
publisher.addObserver(subscriber1);
publisher.addObserver(subscriber2);
publisher.addObserver(subscriber3);
publisher.addObserver(emailService);

// 发布新闻
publisher.publishNews('AI技术突破', 'ChatGPT发布新版本...', '科技');
publisher.publishNews('股市大涨', '今日股市表现强劲...', '财经');
publisher.publishNews('世界杯决赛', '精彩的足球比赛...', '体育');

console.log('\n订阅者收到的新闻数量:');
console.log(`张三: ${subscriber1.getReceivedNews().length}条`);
console.log(`李四: ${subscriber2.getReceivedNews().length}条`);
console.log(`王五: ${subscriber3.getReceivedNews().length}条`);

装饰器模式

动态地给对象添加新功能:

javascript
// 基础组件
class Coffee {
  constructor() {
    this.description = '普通咖啡';
    this.cost = 10;
  }

  getDescription() {
    return this.description;
  }

  getCost() {
    return this.cost;
  }
}

// 装饰器基类
class CoffeeDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

  getDescription() {
    return this.coffee.getDescription();
  }

  getCost() {
    return this.coffee.getCost();
  }
}

// 具体装饰器
class MilkDecorator extends CoffeeDecorator {
  constructor(coffee) {
    super(coffee);
  }

  getDescription() {
    return this.coffee.getDescription() + ', 加牛奶';
  }

  getCost() {
    return this.coffee.getCost() + 3;
  }
}

class SugarDecorator extends CoffeeDecorator {
  constructor(coffee) {
    super(coffee);
  }

  getDescription() {
    return this.coffee.getDescription() + ', 加糖';
  }

  getCost() {
    return this.coffee.getCost() + 1;
  }
}

class WhipDecorator extends CoffeeDecorator {
  constructor(coffee) {
    super(coffee);
  }

  getDescription() {
    return this.coffee.getDescription() + ', 加奶泡';
  }

  getCost() {
    return this.coffee.getCost() + 5;
  }
}

// 使用示例
let coffee = new Coffee();
console.log(`${coffee.getDescription()} - ¥${coffee.getCost()}`);

// 添加装饰
coffee = new MilkDecorator(coffee);
console.log(`${coffee.getDescription()} - ¥${coffee.getCost()}`);

coffee = new SugarDecorator(coffee);
console.log(`${coffee.getDescription()} - ¥${coffee.getCost()}`);

coffee = new WhipDecorator(coffee);
console.log(`${coffee.getDescription()} - ¥${coffee.getCost()}`);
// 最终输出: 普通咖啡, 加牛奶, 加糖, 加奶泡 - ¥19

类与现代JavaScript

类与模块化

javascript
// user.js - 用户模块
export class User {
  constructor(username, email) {
    this.username = username;
    this.email = email;
    this.createdAt = new Date();
  }

  getProfile() {
    return {
      username: this.username,
      email: this.email,
      createdAt: this.createdAt
    };
  }
}

export class AdminUser extends User {
  constructor(username, email, permissions = []) {
    super(username, email);
    this.permissions = permissions;
    this.role = 'admin';
  }

  hasPermission(permission) {
    return this.permissions.includes(permission);
  }

  addPermission(permission) {
    if (!this.hasPermission(permission)) {
      this.permissions.push(permission);
    }
  }
}

// userManager.js - 用户管理模块
import { User, AdminUser } from './user.js';

export class UserManager {
  constructor() {
    this.users = new Map();
  }

  createUser(username, email, isAdmin = false, permissions = []) {
    const user = isAdmin
      ? new AdminUser(username, email, permissions)
      : new User(username, email);

    this.users.set(username, user);
    return user;
  }

  getUser(username) {
    return this.users.get(username);
  }

  getAllUsers() {
    return Array.from(this.users.values());
  }

  deleteUser(username) {
    return this.users.delete(username);
  }
}

// main.js - 主应用
import { UserManager } from './userManager.js';

const userManager = new UserManager();

const regularUser = userManager.createUser('john', 'john@example.com');
const admin = userManager.createUser('admin', 'admin@example.com', true, ['read', 'write', 'delete']);

console.log(regularUser.getProfile());
console.log(admin.hasPermission('delete')); // true

类与异步编程

javascript
class DataService {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    this.cache = new Map();
  }

  async fetchData(endpoint) {
    const url = `${this.baseUrl}/${endpoint}`;

    // 检查缓存
    if (this.cache.has(url)) {
      console.log('从缓存获取数据');
      return this.cache.get(url);
    }

    try {
      console.log(`正在获取数据: ${url}`);
      const response = await fetch(url);

      if (!response.ok) {
        throw new Error(`HTTP错误: ${response.status}`);
      }

      const data = await response.json();

      // 缓存数据
      this.cache.set(url, data);

      return data;
    } catch (error) {
      console.error('获取数据失败:', error);
      throw error;
    }
  }

  async batchFetch(endpoints) {
    const promises = endpoints.map(endpoint => this.fetchData(endpoint));
    return Promise.all(promises);
  }

  clearCache() {
    this.cache.clear();
  }
}

// 使用示例
const dataService = new DataService('https://api.example.com');

async function loadUserData() {
  try {
    const userData = await dataService.fetchData('users/123');
    const userPosts = await dataService.fetchData('users/123/posts');

    console.log('用户数据:', userData);
    console.log('用户帖子:', userPosts);
  } catch (error) {
    console.error('加载用户数据失败:', error);
  }
}

最佳实践

类设计原则

单一职责原则

每个类应该只负责一件事:

javascript
// 不好的设计:一个类承担多个职责
class BadUser {
  constructor(username, email) {
    this.username = username;
    this.email = email;
  }

  // 用户数据验证
  validateEmail() {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email);
  }

  // 数据库操作
  save() {
    // 保存到数据库的逻辑
  }

  // 邮件发送
  sendWelcomeEmail() {
    // 发送欢迎邮件的逻辑
  }

  // 日志记录
  log(message) {
    console.log(`[${new Date()}] ${message}`);
  }
}

// 好的设计:职责分离
class User {
  constructor(username, email) {
    this.username = username;
    this.email = email;
  }

  getProfile() {
    return {
      username: this.username,
      email: this.email
    };
  }
}

class UserValidator {
  static validateEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }

  static validateUsername(username) {
    return username && username.length >= 3;
  }
}

class UserRepository {
  async save(user) {
    // 保存用户到数据库
  }

  async findByUsername(username) {
    // 从数据库查找用户
  }
}

class EmailService {
  async sendWelcomeEmail(user) {
    // 发送欢迎邮件
  }
}

class Logger {
  static log(level, message) {
    console.log(`[${new Date()}] [${level}] ${message}`);
  }
}

开闭原则

对扩展开放,对修改关闭:

javascript
// 基础支付处理器
class PaymentProcessor {
  processPayment(amount, method) {
    throw new Error('processPayment方法必须在子类中实现');
  }
}

// 具体支付方式
class CreditCardProcessor extends PaymentProcessor {
  processPayment(amount, cardInfo) {
    console.log(`使用信用卡支付 ¥${amount}`);
    // 信用卡支付逻辑
    return { success: true, transactionId: 'cc_' + Date.now() };
  }
}

class PayPalProcessor extends PaymentProcessor {
  processPayment(amount, paypalInfo) {
    console.log(`使用PayPal支付 ¥${amount}`);
    // PayPal支付逻辑
    return { success: true, transactionId: 'pp_' + Date.now() };
  }
}

class AlipayProcessor extends PaymentProcessor {
  processPayment(amount, alipayInfo) {
    console.log(`使用支付宝支付 ¥${amount}`);
    // 支付宝支付逻辑
    return { success: true, transactionId: 'ap_' + Date.now() };
  }
}

// 支付管理器
class PaymentManager {
  constructor() {
    this.processors = new Map();
  }

  registerProcessor(type, processor) {
    this.processors.set(type, processor);
  }

  processPayment(type, amount, paymentInfo) {
    const processor = this.processors.get(type);
    if (!processor) {
      throw new Error(`不支持的支付方式: ${type}`);
    }
    return processor.processPayment(amount, paymentInfo);
  }
}

// 使用示例
const paymentManager = new PaymentManager();
paymentManager.registerProcessor('creditcard', new CreditCardProcessor());
paymentManager.registerProcessor('paypal', new PayPalProcessor());
paymentManager.registerProcessor('alipay', new AlipayProcessor());

// 添加新的支付方式不需要修改现有代码
class WeChatPayProcessor extends PaymentProcessor {
  processPayment(amount, wechatInfo) {
    console.log(`使用微信支付 ¥${amount}`);
    return { success: true, transactionId: 'wx_' + Date.now() };
  }
}

paymentManager.registerProcessor('wechat', new WeChatPayProcessor());

里氏替换原则

子类应该能够替换父类:

javascript
// 基类
class Bird {
  fly() {
    console.log('鸟儿在飞翔');
  }
}

// 违反LSP的设计
class Penguin extends Bird {
  fly() {
    throw new Error('企鹅不会飞'); // 违反了LSP
  }
}

// 更好的设计
class Animal {
  move() {
    console.log('动物在移动');
  }
}

class FlyingBird extends Animal {
  move() {
    this.fly();
  }

  fly() {
    console.log('鸟儿在飞翔');
  }
}

class SwimmingBird extends Animal {
  move() {
    this.swim();
  }

  swim() {
    console.log('鸟儿在游泳');
  }
}

class Eagle extends FlyingBird {}
class Penguin extends SwimmingBird {}

function makeAnimalMove(animal) {
  animal.move();
}

makeAnimalMove(new Eagle());
makeAnimalMove(new Penguin());

性能考量

避免在构造函数中创建方法

javascript
// 不好的做法
class BadCounter {
  constructor() {
    this.count = 0;
    this.increment = function() {
      this.count++;
    };
  }
}

// 好的做法
class GoodCounter {
  constructor() {
    this.count = 0;
  }

  increment() {
    this.count++;
  }
}

合理使用私有字段

javascript
class OptimizedClass {
  constructor(publicData, privateData) {
    this.publicData = publicData;
    this.#privateData = privateData;
  }

  #privateData;

  getPrivateData() {
    return this.#privateData;
  }
}

测试友好的类设计

javascript
class UserService {
  constructor(userRepository, emailService, logger) {
    this.userRepository = userRepository;
    this.emailService = emailService;
    this.logger = logger;
  }

  async createUser(userData) {
    try {
      // 验证用户数据
      this.validateUserData(userData);

      // 创建用户
      const user = await this.userRepository.create(userData);

      // 发送欢迎邮件
      await this.emailService.sendWelcomeEmail(user);

      // 记录日志
      this.logger.info(`用户创建成功: ${user.username}`);

      return user;
    } catch (error) {
      this.logger.error(`用户创建失败: ${error.message}`);
      throw error;
    }
  }

  validateUserData(userData) {
    if (!userData.username) {
      throw new Error('用户名不能为空');
    }
    if (!userData.email) {
      throw new Error('邮箱不能为空');
    }
    // 更多验证逻辑...
  }
}

// 测试示例(使用Jest)
describe('UserService', () => {
  let userService;
  let mockUserRepository;
  let mockEmailService;
  let mockLogger;

  beforeEach(() => {
    mockUserRepository = {
      create: jest.fn()
    };
    mockEmailService = {
      sendWelcomeEmail: jest.fn()
    };
    mockLogger = {
      info: jest.fn(),
      error: jest.fn()
    };

    userService = new UserService(mockUserRepository, mockEmailService, mockLogger);
  });

  test('应该成功创建用户', async () => {
    const userData = { username: 'test', email: 'test@example.com' };
    const createdUser = { id: 1, ...userData };

    mockUserRepository.create.mockResolvedValue(createdUser);

    const result = await userService.createUser(userData);

    expect(result).toEqual(createdUser);
    expect(mockUserRepository.create).toHaveBeenCalledWith(userData);
    expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith(createdUser);
    expect(mockLogger.info).toHaveBeenCalled();
  });
});

总结

JavaScript的类语法让面向对象编程变得更加直观。虽然底层仍然是基于原型的,但类提供了更清晰的代码组织方式。

核心概念包括封装、继承、多态和抽象。现代JavaScript支持私有字段、静态成员、访问器属性等特性,让类的功能更加完善。

在实际开发中,合理运用设计模式能够解决常见的编程问题。同时要注意遵循设计原则,编写可维护的代码。

类与函数式编程各有优势,选择合适的编程范式取决于具体的应用场景和团队偏好。

贡献者

huoshan
huoshan

用知识点燃技术的火山