Skip to content

Class 语法:现代 JavaScript 面向对象编程

在现代 JavaScript 开发中,将代码组织成清晰、可复用的结构至关重要。虽然 JavaScript 基于原型的本质非常强大,但直接操作原型往往显得繁琐且晦涩。ES6 引入的 class 语法为我们提供了一种更接近传统面向对象语言的方式来定义对象蓝图,让代码更加整洁、直观且易于维护。

Class 基础语法

基本类定义

javascript
// 基本类定义
class Person {
  // 构造函数
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 实例方法
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }

  // 另一个实例方法
  celebrateBirthday() {
    this.age++;
    console.log(`Happy birthday! Now I'm ${this.age} years old`);
  }
}

// 创建实例
const john = new Person("John", 25);
john.greet(); // Hello, I'm John
john.celebrateBirthday(); // Happy birthday! Now I'm 26 years old

console.log(john.name); // John
console.log(john.age); // 26

类声明 vs 类表达式

javascript
// 类声明
class Animal {
  constructor(species) {
    this.species = species;
  }
}

// 类表达式(匿名)
const Vehicle = class {
  constructor(type) {
    this.type = type;
  }
};

// 类表达式(命名)
const Plant = class Flower {
  constructor(name) {
    this.name = name;
  }
};

const rose = new Flower("Rose");
console.log(rose.name); // Rose

Class 的本质

尽管使用了新的语法,但 JavaScript 中的 Class 本质上是语法糖,底层仍然是基于原型:

javascript
class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
}

// 等价的传统写法
function Person(name) {
  this.name = name;
}

Person.prototype.greet = function () {
  console.log(`Hello, I'm ${this.name}`);
};

// 验证原型关系
const person = new Person("John");
console.log(person.__proto__ === Person.prototype); // true
console.log(typeof Person); // function

继承

extends 关键字

javascript
// 父类
class Animal {
  constructor(name, species) {
    this.name = name;
    this.species = species;
  }

  speak() {
    console.log(`${this.name} the ${this.species} makes a sound`);
  }

  eat() {
    console.log(`${this.name} is eating`);
  }
}

// 子类
class Dog extends Animal {
  constructor(name, breed) {
    // 调用父类构造函数
    super(name, "dog");
    this.breed = breed;
  }

  // 重写父类方法
  speak() {
    console.log(`${this.name} the ${this.breed} barks: Woof!`);
  }

  // 新增方法
  fetch() {
    console.log(`${this.name} is fetching the ball`);
  }

  // 调用父类方法
  eat() {
    // 调用父类的eat方法
    super.eat();
    console.log(`${this.name} enjoys dog food`);
  }
}

const buddy = new Dog("Buddy", "Golden Retriever");

// 调用重写的方法
buddy.speak(); // Buddy the Golden Retriever barks: Woof!

// 调用子类新增的方法
buddy.fetch(); // Buddy is fetching the ball

// 调用增强的父类方法
buddy.eat();
// Buddy is eating
// Buddy enjoys dog food

继承链的验证

javascript
class Animal {}
class Dog extends Animal {}
class GoldenRetriever extends Dog {}

const dog = new GoldenRetriever();

console.log(dog instanceof GoldenRetriever); // true
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true

// 原型链关系
console.log(Object.getPrototypeOf(dog) === GoldenRetriever.prototype); // true
console.log(Object.getPrototypeOf(GoldenRetriever.prototype) === Dog.prototype); // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true

混入(Mixin)模式

javascript
// 定义混入类
const canFly = {
  fly() {
    console.log(`${this.name} is flying`);
  },
  land() {
    console.log(`${this.name} is landing`);
  },
};

const canSwim = {
  swim() {
    console.log(`${this.name} is swimming`);
  },
  dive() {
    console.log(`${this.name} is diving`);
  },
};

// 使用混入
class Bird {
  constructor(name) {
    this.name = name;
  }
}

// 应用混入
Object.assign(Bird.prototype, canFly);

class Duck extends Bird {
  constructor(name) {
    super(name);
  }
}

// Duck同时继承了Bird和应用了canFly
const duck = new Duck("Donald");
duck.fly(); // Donald is flying

// 对于多个混入,可以创建辅助函数
function applyMixins(targetClass, ...mixins) {
  mixins.forEach((mixin) => {
    Object.getOwnPropertyNames(mixin).forEach((name) => {
      if (name !== "constructor") {
        targetClass.prototype[name] = mixin[name];
      }
    });
  });
}

// 应用多个混入
class AmphibiousCar {
  constructor(name) {
    this.name = name;
  }
}

applyMixins(AmphibiousCar, canSwim, canFly);

const amphiCar = new AmphibiousCar("AmphiCar");
amphiCar.swim(); // AmphiCar is swimming
amphiCar.fly(); // AmphiCar is flying

静态方法和静态属性

静态方法

javascript
class MathHelper {
  // 实例方法
  multiply(a, b) {
    return a * b;
  }

  // 静态方法
  static add(a, b) {
    return a + b;
  }

  static subtract(a, b) {
    return a - b;
  }

  // 静态工厂方法
  static createMultiplier(factor) {
    return new (class {
      multiply(x) {
        return x * factor;
      }
    })();
  }
}

// 调用静态方法(不需要实例)
console.log(MathHelper.add(5, 3)); // 8
console.log(MathHelper.subtract(10, 3)); // 7

// 创建实例
const math = new MathHelper();
console.log(math.multiply(4, 3)); // 12

// 静态工厂方法
const doubleMultiplier = MathHelper.createMultiplier(2);
console.log(doubleMultiplier.multiply(5)); // 10

// 静态方法不会被子类继承
class AdvancedMath extends MathHelper {}

console.log(AdvancedMath.add(2, 3)); // 8 (通过原型链访问)
console.log(AdvancedMath.subtract(5, 2)); // 3

静态属性

javascript
class Counter {
  // 静态属性(ES2022)
  static count = 0;
  static instances = [];

  constructor(name) {
    this.name = name;
    this.id = Counter.count++;
    Counter.instances.push(this);
  }

  // 静态getter
  static get totalCount() {
    return this.count;
  }

  static get allInstances() {
    return [...this.instances];
  }

  // 静态方法
  static reset() {
    this.count = 0;
    this.instances.length = 0;
  }

  static getInstanceCount() {
    return this.instances.length;
  }
}

const counter1 = new Counter("Counter 1");
const counter2 = new Counter("Counter 2");
const counter3 = new Counter("Counter 3");

console.log(Counter.totalCount); // 3
console.log(Counter.getInstanceCount()); // 3
console.log(Counter.allInstances.length); // 3

Counter.reset();
console.log(Counter.totalCount); // 0
console.log(Counter.getInstanceCount()); // 0

访问器属性

Getter 和 Setter

javascript
class Person {
  constructor(firstName, lastName, age) {
    this._firstName = firstName;
    this._lastName = lastName;
    this._age = age;
  }

  // Getter for fullName
  get fullName() {
    return `${this._firstName} ${this._lastName}`;
  }

  // Setter for fullName
  set fullName(name) {
    const parts = name.split(" ");
    if (parts.length === 2) {
      this._firstName = parts[0];
      this._lastName = parts[1];
    } else {
      throw new Error("Please provide both first and last name");
    }
  }

  // Getter for age
  get age() {
    return this._age;
  }

  // Setter for age with validation
  set age(newAge) {
    if (typeof newAge !== "number" || newAge < 0 || newAge > 150) {
      throw new Error("Age must be a number between 0 and 150");
    }
    this._age = newAge;
  }

  // 只读属性
  get isAdult() {
    return this._age >= 18;
  }
}

const person = new Person("John", "Doe", 25);

console.log(person.fullName); // John Doe
console.log(person.age); // 25
console.log(person.isAdult); // true

// 使用setter
person.fullName = "Jane Smith";
console.log(person.fullName); // Jane Smith
console.log(person._firstName); // Jane

// 使用验证
person.age = 30; // 正常
console.log(person.age); // 30

try {
  person.age = -5; // 抛出错误
} catch (error) {
  console.log(error.message); // Age must be a number between 0 and 150
}

私有字段和方法

私有字段(ES2022)

javascript
class BankAccount {
  // 私有字段(以#开头)
  #balance;
  #accountNumber;
  #transactions = [];

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

  // 公共方法
  deposit(amount) {
    if (amount <= 0) {
      throw new Error("Deposit amount must be positive");
    }
    this.#balance += amount;
    this.#transactions.push({ type: "deposit", amount, date: new Date() });
    return this.#balance;
  }

  withdraw(amount) {
    if (amount <= 0) {
      throw new Error("Withdrawal amount must be positive");
    }
    if (amount > this.#balance) {
      throw new Error("Insufficient funds");
    }
    this.#balance -= amount;
    this.#transactions.push({ type: "withdrawal", amount, date: new Date() });
    return this.#balance;
  }

  // 访问私有字段
  getBalance() {
    return this.#balance;
  }

  getAccountNumber() {
    return this.#accountNumber;
  }

  getTransactionHistory() {
    return [...this.#transactions]; // 返回副本
  }

  // 私有方法
  #validateAmount(amount) {
    return typeof amount === "number" && amount > 0;
  }

  // 私有getter
  get #isOverdrawn() {
    return this.#balance < 0;
  }

  // 使用私有方法
  processTransaction(amount, type) {
    if (!this.#validateAmount(amount)) {
      throw new Error("Invalid amount");
    }

    if (type === "deposit") {
      return this.deposit(amount);
    } else if (type === "withdrawal") {
      return this.withdraw(amount);
    }
  }
}

const account = new BankAccount("123456789", 1000);

// 公共访问
console.log(account.getBalance()); // 1000
account.deposit(500);
console.log(account.getBalance()); // 1500

// 私有字段无法从外部访问
console.log(account.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class
console.log(account.#accountNumber); // SyntaxError: Private field '#accountNumber' must be declared in an enclosing class

传统的私有模式(兼容旧版本)

javascript
class TraditionalPrivate {
  constructor(name) {
    // 使用弱映射存储私有数据
    this._privateData = new Map();
    this._privateData.set(this, {
      name: name,
      secrets: [],
    });
  }

  getName() {
    return this._privateData.get(this).name;
  }

  setName(name) {
    this._privateData.get(this).name = name;
  }

  addSecret(secret) {
    this._privateData.get(this).secrets.push(secret);
  }

  getSecrets() {
    return [...this._privateData.get(this).secrets];
  }
}

// 或者使用闭包模式
function createPrivateClass() {
  const privateData = new WeakMap();

  return class PrivateExample {
    constructor(name) {
      privateData.set(this, { name, secrets: [] });
    }

    getName() {
      return privateData.get(this).name;
    }

    addSecret(secret) {
      privateData.get(this).secrets.push(secret);
    }
  };
}

const PrivateExample = createPrivateClass();

方法的高级用法

绑定方法

javascript
class Button {
  constructor(text) {
    this.text = text;
    // 确保this绑定正确
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(`Button "${this.text}" clicked`);
  }

  // 箭头函数方法(自动绑定)
  handleMouseOver = () => {
    console.log(`Mouse over "${this.text}"`);
  };

  // 普通方法(需要绑定)
  handleMouseOut() {
    console.log(`Mouse out "${this.text}"`);
  }
}

const button = new Button("Click Me");

// DOM事件监听
// document.getElementById('myButton').addEventListener('click', button.handleClick);
// document.getElementById('myButton').addEventListener('mouseover', button.handleMouseOver);
// document.getElementById('myButton').addEventListener('mouseout', button.handleMouseOut.bind(button));

计算属性名

javascript
const methodName = "greet";
const propertyName = "fullName";

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  // 计算方法名
  [methodName]() {
    console.log(`Hello, I'm ${this.firstName}`);
  }

  // 计算getter名
  get [propertyName]() {
    return `${this.firstName} ${this.lastName}`;
  }

  // 计算setter名
  set [propertyName](name) {
    const parts = name.split(" ");
    this.firstName = parts[0] || "";
    this.lastName = parts[1] || "";
  }
}

const person = new Person("John", "Doe");
person.greet(); // Hello, I'm John
console.log(person.fullName); // John Doe

person.fullName = "Jane Smith";
console.log(person.fullName); // Jane Smith

Class 的内置特性

new.target

javascript
class Shape {
  constructor(type) {
    this.type = type;

    // 检查是否通过new调用
    if (!new.target) {
      throw new Error("Shape must be called with new");
    }

    // new.target指向被new的构造函数
    console.log("Creating instance of:", new.target.name);
  }
}

class Circle extends Shape {
  constructor(radius) {
    // 调用父类构造函数时,new.target是Circle
    super("circle");
    this.radius = radius;
  }
}

const shape = new Shape("polygon"); // Creating instance of: Shape
const circle = new Circle(5); // Creating instance of: Circle

// 抽象类模式
class Animal {
  constructor() {
    if (new.target === Animal) {
      throw new Error("Animal is abstract and cannot be instantiated directly");
    }
  }

  speak() {
    throw new Error("Method must be implemented by subclass");
  }
}

class Dog extends Animal {
  speak() {
    console.log("Woof!");
  }
}

const dog = new Dog();
dog.speak(); // Woof!

// const animal = new Animal(); // Error: Animal is abstract and cannot be instantiated directly

Symbol.species

javascript
class MyArray extends Array {
  static get [Symbol.species]() {
    return Array; // 重写species为Array而不是MyArray
  }

  // 自定义方法
  first() {
    return this[0];
  }

  last() {
    return this[this.length - 1];
  }
}

const myArray = new MyArray(1, 2, 3, 4, 5);

// map方法返回普通Array而不是MyArray
const mapped = myArray.map((x) => x * 2);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true

// 自定义方法仍然工作
console.log(myArray.first()); // 1
console.log(myArray.last()); // 5

实际应用示例

数据模型类

javascript
class UserModel {
  #id;
  #email;
  #name;
  #createdAt;
  #updatedAt;

  constructor({ id, email, name }) {
    this.#id = id || this.#generateId();
    this.#email = this.#validateEmail(email);
    this.#name = name;
    this.#createdAt = new Date();
    this.#updatedAt = new Date();
  }

  // 公共访问器
  get id() {
    return this.#id;
  }

  get email() {
    return this.#email;
  }

  get name() {
    return this.#name;
  }

  get createdAt() {
    return this.#createdAt;
  }

  get updatedAt() {
    return this.#updatedAt;
  }

  // 设置器
  set email(newEmail) {
    this.#email = this.#validateEmail(newEmail);
    this.#updatedAt = new Date();
  }

  set name(newName) {
    this.#name = newName;
    this.#updatedAt = new Date();
  }

  // 实例方法
  update(data) {
    if (data.email) this.email = data.email;
    if (data.name) this.name = data.name;
    return this;
  }

  toJSON() {
    return {
      id: this.#id,
      email: this.#email,
      name: this.#name,
      createdAt: this.#createdAt,
      updatedAt: this.#updatedAt,
    };
  }

  // 私有方法
  #generateId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
  }

  #validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
      throw new Error("Invalid email format");
    }
    return email;
  }

  // 静态方法
  static create(userData) {
    return new UserModel(userData);
  }

  static fromJSON(json) {
    const data = typeof json === "string" ? JSON.parse(json) : json;
    return new UserModel(data);
  }
}

// 使用示例
const user = UserModel.create({
  email: "[email protected]",
  name: "John Doe",
});

console.log(user.email); // [email protected]
console.log(user.name); // John Doe

user.update({ name: "John Smith" });
console.log(user.name); // John Smith

const userJSON = user.toJSON();
console.log(userJSON);

const newUser = UserModel.fromJSON(userJSON);
console.log(newUser.email); // [email protected]

状态管理类

javascript
class StateManager {
  #state = {};
  #listeners = new Map();
  #history = [];
  #maxHistorySize = 50;

  constructor(initialState = {}) {
    this.#state = { ...initialState };
    this.#saveToHistory();
  }

  // 获取状态
  getState(path) {
    if (!path) return { ...this.#state };

    return path.split(".").reduce((obj, key) => obj?.[key], this.#state);
  }

  // 设置状态
  setState(path, value) {
    const oldValue = this.getState(path);
    const newValue = typeof path === "string" ? value : path;

    if (typeof path === "string") {
      // 更新嵌套属性
      this.#updateNestedProperty(path, newValue);
    } else {
      // 更新整个状态
      this.#state = { ...this.#state, ...newValue };
    }

    this.#saveToHistory();
    this.#notifyListeners(path, newValue, oldValue);

    return this;
  }

  // 订阅状态变化
  subscribe(path, callback) {
    if (!this.#listeners.has(path)) {
      this.#listeners.set(path, new Set());
    }

    this.#listeners.get(path).add(callback);

    // 返回取消订阅函数
    return () => {
      const listeners = this.#listeners.get(path);
      if (listeners) {
        listeners.delete(callback);
        if (listeners.size === 0) {
          this.#listeners.delete(path);
        }
      }
    };
  }

  // 撤销
  undo() {
    if (this.#history.length > 1) {
      this.#history.pop(); // 移除当前状态
      const previousState = this.#history[this.#history.length - 1];
      this.#state = { ...previousState };
      return true;
    }
    return false;
  }

  // 重做
  redo() {
    // 实现重做逻辑
  }

  // 清除历史
  clearHistory() {
    this.#history = [{ ...this.#state }];
    return this;
  }

  // 私有方法
  #updateNestedProperty(path, value) {
    const keys = path.split(".");
    const lastKey = keys.pop();
    const target = keys.reduce((obj, key) => {
      if (!obj[key]) obj[key] = {};
      return obj[key];
    }, this.#state);

    target[lastKey] = value;
  }

  #saveToHistory() {
    this.#history.push({ ...this.#state });
    if (this.#history.length > this.#maxHistorySize) {
      this.#history.shift();
    }
  }

  #notifyListeners(path, newValue, oldValue) {
    // 通知特定路径的监听器
    const specificListeners = this.#listeners.get(path);
    if (specificListeners) {
      specificListeners.forEach((callback) => {
        try {
          callback(newValue, oldValue, path);
        } catch (error) {
          console.error("Listener error:", error);
        }
      });
    }

    // 通知通配符监听器
    const wildcardListeners = this.#listeners.get("*");
    if (wildcardListeners) {
      wildcardListeners.forEach((callback) => {
        try {
          callback(this.#state, { path, newValue, oldValue });
        } catch (error) {
          console.error("Wildcard listener error:", error);
        }
      });
    }
  }
}

// 使用示例
const stateManager = new StateManager({
  user: { name: "John", age: 25 },
  theme: "light",
});

// 订阅状态变化
const unsubscribeUser = stateManager.subscribe("user", (newUser, oldUser) => {
  console.log("User changed:", { from: oldUser, to: newUser });
});

const unsubscribeTheme = stateManager.subscribe(
  "theme",
  (newTheme, oldTheme) => {
    console.log("Theme changed:", { from: oldTheme, to: newTheme });
  }
);

// 更新状态
stateManager.setState("user.name", "Jane");
stateManager.setState("theme", "dark");

// 撤销
stateManager.undo();

总结

JavaScript 的 Class 语法为面向对象编程提供了更清晰、更直观的语法,同时保持了基于原型的底层机制:

  • 清晰的语法结构:使用classconstructorextends等关键字
  • 继承机制简化:通过extendssuper轻松实现继承
  • 私有字段支持:使用#语法定义真正私有字段
  • 静态方法属性:支持类级别的成员
  • 访问器属性:通过 getter 和 setter 提供灵活的属性访问
  • 向后兼容:底层仍然是原型继承,与现有代码兼容

虽然 Class 语法让 JavaScript 看起来更像传统的面向对象语言,但重要的是要理解它仍然是基于原型继承的。掌握 Class 语法的同时理解底层机制,能帮助我们写出更现代、更易维护的 JavaScript 代码。