Skip to content

JavaScript 类

什么是类

类(Class)是 ES6(ECMAScript 2015)引入的一种新语法,用于创建对象和实现面向对象编程。类是一种模板,用于定义对象的属性和方法。

在 ES6 之前,JavaScript 使用构造函数和原型链来实现面向对象编程。ES6 类语法提供了一种更简洁、更清晰的方式来定义类,使得 JavaScript 的面向对象编程更加接近传统的面向对象语言(如 Java、C++)。

类的基本语法

1. 类的定义

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

  // 实例方法
  greet() {
    return `Hello, my name is ${this.name}`;
  }

  // 静态方法
  static create(name, age) {
    return new Person(name, age);
  }
}

// 使用类
const person = new Person("John", 30);
console.log(person.name); // 'John'
console.log(person.age); // 30
console.log(person.greet()); // 'Hello, my name is John'

// 使用静态方法
const person2 = Person.create("Jane", 25);
console.log(person2.name); // 'Jane'
console.log(person2.age); // 25

2. 类的表达式

类也可以使用表达式的形式定义,类似于函数表达式。

javascript
// 类表达式
const Person = class {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `Hello, my name is ${this.name}`;
  }
};

// 命名类表达式
const Person = class PersonClass {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `Hello, my name is ${this.name}`;
  }
};

// 使用类
const person = new Person("John", 30);
console.log(person.greet()); // 'Hello, my name is John'

3. 类的继承

类可以通过 extends 关键字继承其他类。

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

  greet() {
    return `Hello, my name is ${this.name}`;
  }
}

// 子类
class Student extends Person {
  constructor(name, age, grade) {
    // 调用父类的构造函数
    super(name, age);
    this.grade = grade;
  }

  // 重写父类的方法
  greet() {
    return `Hello, my name is ${this.name}, I'm in grade ${this.grade}`;
  }

  // 子类的方法
  study() {
    return `${this.name} is studying`;
  }
}

// 使用子类
const student = new Student("John", 15, 9);
console.log(student.name); // 'John'
console.log(student.age); // 15
console.log(student.grade); // 9
console.log(student.greet()); // 'Hello, my name is John, I'm in grade 9'
console.log(student.study()); // 'John is studying'

// 检查实例
console.log(student instanceof Student); // true
console.log(student instanceof Person); // true

4. 静态方法和属性

静态方法和属性属于类本身,而不是类的实例。

javascript
class Person {
  // 静态属性
  static species = "Homo sapiens";

  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 实例方法
  greet() {
    return `Hello, my name is ${this.name}`;
  }

  // 静态方法
  static create(name, age) {
    return new Person(name, age);
  }

  // 静态方法
  static getSpecies() {
    return this.species;
  }
}

// 访问静态属性
console.log(Person.species); // 'Homo sapiens'

// 调用静态方法
const person = Person.create("John", 30);
console.log(person.name); // 'John'

console.log(Person.getSpecies()); // 'Homo sapiens'

// 错误:静态属性和方法不能通过实例访问
// console.log(person.species); // undefined
// console.log(person.create); // undefined

5. Getter 和 Setter

Getter 和 Setter 允许我们定义对象属性的读取和设置行为。

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

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

  // Setter
  set age(value) {
    if (value < 0) {
      throw new Error("年龄不能为负数");
    }
    this._age = value;
  }

  // Getter
  get fullName() {
    return this.name;
  }
}

// 使用 Getter 和 Setter
const person = new Person("John", 30);
console.log(person.age); // 30(调用 getter)

person.age = 31; // 调用 setter
console.log(person.age); // 31

// 错误:年龄不能为负数
// person.age = -1; // Error: 年龄不能为负数

console.log(person.fullName); // 'John'(调用 getter)

6. 私有属性和方法

私有属性和方法是 ES2022 引入的特性,使用 # 前缀表示,只能在类内部访问。

javascript
class Person {
  // 私有属性
  #name;
  #age;

  constructor(name, age) {
    this.#name = name;
    this.#age = age;
  }

  // 实例方法访问私有属性
  greet() {
    return `Hello, my name is ${this.#name}, I'm ${this.#age} years old`;
  }

  // 私有方法
  #validateAge(age) {
    return age >= 0;
  }

  // 实例方法调用私有方法
  setAge(age) {
    if (this.#validateAge(age)) {
      this.#age = age;
    } else {
      throw new Error("年龄不能为负数");
    }
  }

  getAge() {
    return this.#age;
  }
}

// 使用类
const person = new Person("John", 30);
console.log(person.greet()); // 'Hello, my name is John, I'm 30 years old'

person.setAge(31);
console.log(person.getAge()); // 31

// 错误:私有属性不能从外部访问
// console.log(person.#name); // SyntaxError: Private field '#name' must be declared in an enclosing class

// 错误:私有方法不能从外部访问
// person.#validateAge(32); // SyntaxError: Private field '#validateAge' must be declared in an enclosing class

7. 类的字段初始化

类的字段初始化允许我们在类定义中直接初始化实例属性,而不需要在构造函数中赋值。

javascript
class Person {
  // 实例属性初始化
  name = "Unknown";
  age = 0;

  // 静态属性初始化
  static species = "Homo sapiens";

  constructor(name, age) {
    if (name) {
      this.name = name;
    }
    if (age) {
      this.age = age;
    }
  }

  greet() {
    return `Hello, my name is ${this.name}`;
  }
}

// 使用类
const person1 = new Person("John", 30);
console.log(person1.name); // 'John'
console.log(person1.age); // 30

const person2 = new Person();
console.log(person2.name); // 'Unknown'
console.log(person2.age); // 0

console.log(Person.species); // 'Homo sapiens'

类的特性

1. 严格模式

类内部默认在严格模式下运行,不需要显式添加 'use strict;'

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

  greet() {
    // 严格模式下,this 为 undefined(如果不是通过实例调用)
    return `Hello, my name is ${this.name}`;
  }
}

const person = new Person("John");
console.log(person.greet()); // 'Hello, my name is John'

// 错误:在严格模式下,this 为 undefined
const greet = person.greet;
// console.log(greet()); // TypeError: Cannot read property 'name' of undefined

2. 构造函数

构造函数是类的特殊方法,当使用 new 关键字创建实例时会调用。

  • 每个类只能有一个构造函数
  • 如果没有定义构造函数,会默认生成一个空的构造函数
  • 子类必须在构造函数中调用 super() 来调用父类的构造函数
javascript
// 没有构造函数的类
class Person {
  name = "Unknown";

  greet() {
    return `Hello, my name is ${this.name}`;
  }
}

const person = new Person();
console.log(person.name); // 'Unknown'

// 子类必须调用 super()
class Student extends Person {
  constructor(name, grade) {
    // 必须先调用 super()
    super(name);
    this.grade = grade;
  }
}

const student = new Student("John", 9);
console.log(student.name); // 'John'
console.log(student.grade); // 9

3. 方法定义

类中的方法定义是简写形式,不需要使用 function 关键字。

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

  // 方法定义(简写形式)
  greet() {
    return `Hello, my name is ${this.name}`;
  }

  // 错误:不能使用 function 关键字
  // function greet() {
  //   return `Hello, my name is ${this.name}`;
  // }
}

4. 原型链

类在底层仍然使用原型链实现,类的方法会被添加到类的原型对象上。

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

  greet() {
    return `Hello, my name is ${this.name}`;
  }
}

const person = new Person("John");

// 方法存在于原型对象上
console.log(person.greet === Person.prototype.greet); // true

// 可以通过原型对象添加方法
Person.prototype.sayGoodbye = function () {
  return `Goodbye, ${this.name}`;
};

console.log(person.sayGoodbye()); // 'Goodbye, John'

5. 类的提升

类声明不会被提升,必须在定义后使用。

javascript
// 错误:类声明不会被提升
// const person = new Person('John'); // ReferenceError: Person is not defined

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

// 正确:在定义后使用
const person = new Person("John");
console.log(person.name); // 'John'

类的应用场景

1. 面向对象编程

类是实现面向对象编程的重要工具,它允许我们创建具有相似属性和方法的对象。

javascript
// 面向对象编程示例
class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    return `${this.name} makes a sound`;
  }
}

class Dog extends Animal {
  speak() {
    return `${this.name} barks`;
  }
}

class Cat extends Animal {
  speak() {
    return `${this.name} meows`;
  }
}

const dog = new Dog("Rex");
console.log(dog.speak()); // 'Rex barks'

const cat = new Cat("Whiskers");
console.log(cat.speak()); // 'Whiskers meows'

2. 组件开发

在前端框架(如 React、Vue)中,类可以用于创建组件。

javascript
// React 组件示例
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.increment()}>Increment</button>
      </div>
    );
  }
}

// Vue 组件示例
class CounterComponent {
  constructor(el) {
    this.el = el;
    this.count = 0;
    this.render();
    this.bindEvents();
  }

  render() {
    this.el.innerHTML = `
      <p>Count: ${this.count}</p>
      <button>Increment</button>
    `;
  }

  bindEvents() {
    this.el.querySelector("button").addEventListener("click", () => {
      this.count++;
      this.render();
      this.bindEvents();
    });
  }
}

// 使用组件
new CounterComponent(document.getElementById("counter"));

3. 工具类

类可以用于创建工具类,提供一组相关的静态方法。

javascript
// 工具类示例
class MathUtils {
  // 静态方法
  static add(a, b) {
    return a + b;
  }

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

  static multiply(a, b) {
    return a * b;
  }

  static divide(a, b) {
    if (b === 0) {
      throw new Error("除数不能为 0");
    }
    return a / b;
  }

  // 静态方法
  static factorial(n) {
    if (n < 0) {
      throw new Error("参数不能为负数");
    }
    if (n === 0 || n === 1) {
      return 1;
    }
    return n * this.factorial(n - 1);
  }
}

// 使用工具类
console.log(MathUtils.add(2, 3)); // 5
console.log(MathUtils.subtract(5, 2)); // 3
console.log(MathUtils.multiply(2, 3)); // 6
console.log(MathUtils.divide(6, 2)); // 3
console.log(MathUtils.factorial(5)); // 120

4. 数据模型

类可以用于创建数据模型,表示应用中的实体。

javascript
// 数据模型示例
class User {
  constructor(id, name, email) {
    this.id = id;
    this.name = name;
    this.email = email;
    this.createdAt = new Date();
  }

  // 实例方法
  toJSON() {
    return {
      id: this.id,
      name: this.name,
      email: this.email,
      createdAt: this.createdAt,
    };
  }

  // 静态方法
  static fromJSON(json) {
    return new User(json.id, json.name, json.email);
  }

  // 静态方法
  static validate(user) {
    return !!(user.name && user.email);
  }
}

// 使用数据模型
const user = new User(1, "John", "john@example.com");
console.log(user.toJSON()); // { id: 1, name: 'John', email: 'john@example.com', createdAt: Date }

const userJson = { id: 2, name: "Jane", email: "jane@example.com" };
const user2 = User.fromJSON(userJson);
console.log(user2.name); // 'Jane'

console.log(User.validate(user)); // true
console.log(User.validate({ name: "John" })); // false

5. 单例模式

类可以用于实现单例模式,确保一个类只有一个实例。

javascript
// 单例模式示例
class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    this.init();
    Singleton.instance = this;
  }

  init() {
    this.value = 0;
  }

  increment() {
    this.value++;
    return this.value;
  }

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

// 使用单例
const instance1 = new Singleton();
console.log(instance1.increment()); // 1

const instance2 = new Singleton();
console.log(instance2.increment()); // 2
console.log(instance1 === instance2); // true

const instance3 = Singleton.getInstance();
console.log(instance3.increment()); // 3
console.log(instance1 === instance3); // true

类的最佳实践

1. 命名规范

  • 类名:使用 PascalCase(首字母大写)
  • 方法名:使用 camelCase(首字母小写)
  • 属性名:使用 camelCase(首字母小写)
  • 私有属性和方法:使用 # 前缀
javascript
// 好的命名
class Person {
  #privateField;

  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.#privateField = "private";
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  #privateMethod() {
    // 私有方法
  }
}

// 不好的命名
class person {
  _privateField;

  constructor(first_name, last_name) {
    this.first_name = first_name;
    this.last_name = last_name;
  }

  get_full_name() {
    return `${this.first_name} ${this.last_name}`;
  }
}

2. 单一职责

每个类应该只负责一个特定的功能,保持类的简洁和专注。

javascript
// 好的做法:单一职责
class User {
  constructor(id, name, email) {
    this.id = id;
    this.name = name;
    this.email = email;
  }

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

class UserService {
  static async getUser(id) {
    const response = await fetch(`/api/users/${id}`);
    return User.fromJSON(await response.json());
  }

  static async saveUser(user) {
    const response = await fetch("/api/users", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(user.toJSON()),
    });
    return User.fromJSON(await response.json());
  }
}

// 不好的做法:多个职责
class User {
  constructor(id, name, email) {
    this.id = id;
    this.name = name;
    this.email = email;
  }

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

  // 不应该在 User 类中处理 API 请求
  async save() {
    const response = await fetch("/api/users", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(this.toJSON()),
    });
    return User.fromJSON(await response.json());
  }
}

3. 继承的使用

  • 只在真正需要继承关系时使用继承
  • 遵循 Liskov 替换原则(子类应该能够替换父类)
  • 避免深层继承,优先使用组合
javascript
// 好的做法:合理使用继承
class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    return `${this.name} makes a sound`;
  }
}

class Dog extends Animal {
  speak() {
    return `${this.name} barks`;
  }
}

// 不好的做法:深层继承
class Animal {
  // ...
}

class Mammal extends Animal {
  // ...
}

class Carnivore extends Mammal {
  // ...
}

class Dog extends Carnivore {
  // ...
}

4. 构造函数

  • 构造函数应该简洁,只负责初始化实例属性
  • 避免在构造函数中执行复杂的逻辑
  • 对于复杂的初始化,可以使用静态工厂方法
javascript
// 好的做法:简洁的构造函数
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 静态工厂方法
  static createAdult(name) {
    return new Person(name, 18);
  }

  static createChild(name) {
    return new Person(name, 10);
  }
}

// 不好的做法:复杂的构造函数
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;

    // 不应该在构造函数中执行复杂的逻辑
    this.validate();
    this.loadData();
    this.initEvents();
  }

  validate() {
    // 验证逻辑
  }

  loadData() {
    // 加载数据
  }

  initEvents() {
    // 初始化事件
  }
}

5. 错误处理

  • 在类中适当处理错误,使用 try-catch 捕获异常
  • 对于无效的参数,抛出明确的错误信息
  • 对于异步操作,返回 Promise 并正确处理错误
javascript
// 好的做法:错误处理
class Calculator {
  divide(a, b) {
    if (b === 0) {
      throw new Error("除数不能为 0");
    }
    return a / b;
  }

  async fetchData(url) {
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      return await response.json();
    } catch (error) {
      console.error("Error fetching data:", error);
      throw error;
    }
  }
}

// 使用
const calculator = new Calculator();
try {
  console.log(calculator.divide(6, 2)); // 3
  console.log(calculator.divide(6, 0)); // 抛出错误
} catch (error) {
  console.error("Error:", error.message);
}

类的兼容性

1. 浏览器兼容性

特性ChromeFirefoxSafariEdgeIE
基本类语法49+45+9.1+13+不支持
类的继承49+45+9.1+13+不支持
静态方法49+45+9.1+13+不支持
Getter 和 Setter49+45+9.1+13+不支持
类的字段初始化72+69+14.1+79+不支持
私有属性和方法80+74+14.1+80+不支持

2. 转译工具

为了在旧版本浏览器中使用类,可以使用转译工具如 Babel 进行转译。

javascript
// Babel 配置示例 (.babelrc)
{
  "presets": ["@babel/preset-env"]
}

3. 替代方案

在不支持类的环境中,可以使用构造函数和原型链来实现类似的功能。

javascript
// 构造函数和原型链
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function () {
  return `Hello, my name is ${this.name}`;
};

// 静态方法
Person.create = function (name, age) {
  return new Person(name, age);
};

// 继承
function Student(name, age, grade) {
  Person.call(this, name, age);
  this.grade = grade;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.greet = function () {
  return `Hello, my name is ${this.name}, I'm in grade ${this.grade}`;
};

// 使用
const person = new Person("John", 30);
console.log(person.greet()); // 'Hello, my name is John'

const student = new Student("Jane", 15, 9);
console.log(student.greet()); // 'Hello, my name is Jane, I'm in grade 9'

总结

类是 ES6 引入的一种新语法,用于创建对象和实现面向对象编程。它具有以下特点:

  1. 简洁的语法:提供了更清晰、更简洁的语法来定义类和方法
  2. 继承:支持通过 extends 关键字继承其他类
  3. 静态方法和属性:支持定义属于类本身的方法和属性
  4. Getter 和 Setter:允许定义对象属性的读取和设置行为
  5. 私有属性和方法:支持定义只能在类内部访问的属性和方法
  6. 严格模式:默认在严格模式下运行
  7. 原型链:底层仍然使用原型链实现

类的应用场景非常广泛,包括:

  • 面向对象编程
  • 组件开发
  • 工具类
  • 数据模型
  • 设计模式(如单例模式)

通过合理使用类,我们可以编写更加结构化、可维护和可扩展的 JavaScript 代码。