人人都会AI编程

11.6 Class 类语法

更新时间:2026-07-11

在 ES6 之前,JavaScript 通过构造函数和原型来模拟传统的面向对象编程。ES6 引入了 class 关键字,让代码写法更接近 Java、C++ 等语言的“类”,但它的底层仍然是原型继承。理解这一点至关重要:class 并不是新的继承模型,而是一层更清晰、更易读的语法糖

11.6.1 类的定义与构造函数

使用 class 关键字定义一个类,内部必须有一个 constructor 方法(如果没有显式定义,引擎会自动添加一个空构造函数)。对象的实例化仍然使用 new 关键字。

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

  greet() {
    console.log(`你好,我是${this.name},今年${this.age}岁。`);
  }
}

const alice = new Person('Alice', 25);
alice.greet(); // 你好,我是Alice,今年25岁。

等价于传统的构造函数写法:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.greet = function() {
  console.log(`你好,我是${this.name},今年${this.age}岁。`);
};

可见,class 中的方法仍然定义在 Person.prototype 上,实例通过原型链访问它们。

11.6.2 实例方法与静态方法

  • 实例方法:直接定义在 class 内部的方法,会被添加到原型对象上,所有实例共享。
  • 静态方法:使用 static 关键字定义的方法,属于类本身,直接通过类名调用,不能通过实例调用。常用于工具函数、工厂方法等。
class MathUtils {
  static add(a, b) {
    return a + b;
  }
  
  multiply(a, b) {
    return a * b;
  }
}

console.log(MathUtils.add(3, 5)); // 8

const utils = new MathUtils();
console.log(utils.multiply(3, 5)); // 15
// utils.add(3, 5); // 报错:utils.add is not a function

静态属性也可以在类内部直接定义(较新语法):

class Config {
  static version = '1.0.0';
}
console.log(Config.version); // 1.0.0

11.6.3 实例属性的新写法

除了在 constructor 中通过 this 定义实例属性外,ES6+ 允许直接在类体中声明实例属性,无需 constructor 包裹,这样可以让代码更简洁(尤其是配合 TypeScript 的类型标注)。

class User {
  name = '默认用户名'; // 直接声明实例属性,等价于在 constructor 中 this.name = ...
  #age = 0;            // 私有属性(见后文)

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

11.6.4 getter 与 setter

使用 getset 关键字可以为属性定义访问器,在读取或赋值时执行逻辑,常用于数据校验或计算属性。

class Temperature {
  constructor(celsius) {
    this._celsius = celsius; // 通常用下划线前缀表示内部属性
  }

  get fahrenheit() {
    return this._celsius * 9 / 5 + 32;
  }

  set fahrenheit(value) {
    this._celsius = (value - 32) * 5 / 9;
  }
}

const temp = new Temperature(25);
console.log(temp.fahrenheit); // 77
temp.fahrenheit = 100;         // 触发 setter
console.log(temp._celsius);    // 37.777...

11.6.5 继承与 super 关键字

通过 extends 关键字可以轻松实现继承,子类可以复用父类的属性和方法。super 关键字用于访问父类的构造函数或方法。

  • 调用父类构造函数:子类的 constructor 中必须先调用 super() 才能使用 this
  • 在子类方法中调用父类方法:使用 super.methodName()
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} 发出声音`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);        // 必须调用 super
    this.breed = breed;
  }
  speak() {
    super.speak();      // 调用父类的 speak
    console.log('汪汪!');
  }
}

const dog = new Dog('旺财', '中华田园犬');
dog.speak();
// 旺财 发出声音
// 汪汪!

原型链关系:Dog.prototype 的原型是 Animal.prototypeDog 的原型(proto)是 Animal,从而实现了完整的继承。

console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true
console.log(Object.getPrototypeOf(Dog) === Animal); // true

11.6.6 私有属性和方法(# 前缀)

ES2022 引入了私有字段,通过在属性或方法名前加 # 声明真正的私有成员,它只能在类的内部访问,外部或子类都无法直接读写。

class Counter {
  #count = 0; // 私有属性

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

  get value() {
    return this.#count;
  }
}

const c = new Counter();
c.increment();
console.log(c.value);   // 1
// console.log(c.#count); // SyntaxError: Private field '#count' must be declared in an enclosing class

私有方法和 getter/setter 同样使用 # 前缀。注意,私有属性必须先在类体中声明,不能在 constructor 中动态创建。

11.6.7 Class 的底层本质

再次强调:class 本质上仍然是函数。

class Example {}
console.log(typeof Example); // "function"

它做的事情与手动构造函数加原型赋值是完全对应的。class 带来的优势主要是语法的清晰、强制使用 new(否则报错)、允许 super 以及未来更强的私有性支持。理解原型链始终是理解 class 行为的前提——instanceofprototypeproto 等底层机制依然适用。

11.6.8 使用时的注意事项

  1. 变量提升差异class 声明不会像函数声明那样提升,必须先定义后使用(存在暂时性死区)。
  2. 方法不可遍历class 中定义的方法默认是不可枚举的(enumerable: false),与手动挂在 prototype 上的默认行为不同(手动添加是可枚举的)。这可以避免 for...in 遍历时意外取出方法。
  3. 使用 extends 时要关注 this:子类没有自己的 this,需要通过 super 借用父类的 this
  4. 私有字段的转译限制# 私有字段在旧环境无法被 polyfill 完美模拟,需要 Babel 等工具转译,且转译出的代码依赖 WeakMap 等特性。

掌握 class 语法后,你将能够以组织更清晰、可读性更强的方式构建对象体系,同时保持对底层原型机制的深刻理解——这种“知其然,也知其所以然”的状态,正是 JavaScript 开发者不断精进的关键。