Skip to content

ES6 - 类

面向对象编程(Object-Oriented Programming, OOP)是一种使用对象及其交互来设计和编程应用程序的范式。它将软件建模为对象的集合,这些对象是类的实例,通过方法进行通信。ES6 引入了 class 语法,它简化了对象的创建和继承的实现,为习惯于基于类的语言的开发人员提供了一种更清晰、更熟悉的结构。这种语法主要是 JavaScript 现有基于原型继承的语法糖(syntactic sugar)。

让我们回顾一下基本的 OOP 概念:

  • 对象 (Object): 类的一个实例,代表一个真实世界的实体或抽象概念。每个对象都有:
    • 状态 (State): 由其属性(数据成员或属性)描述。
    • 行为 (Behavior): 由其方法(与对象关联的函数)定义。
    • 身份 (Identity): 一个唯一的引用,用于将其与其他对象区分开。
  • 类 (Class): 创建对象的蓝图或模板。它封装了数据(属性)和函数(方法),这些数据和函数定义了对象。
  • 方法 (Method): 与类或对象关联的函数,定义其行为并实现通信。
  • 封装 (Encapsulation): 将数据(属性)和操作这些数据的方法捆绑到单个单元(类)中。ES6+ 类通过公共和私有成员支持这一点。
  • 继承 (Inheritance): 允许一个新类(子类或派生类)从现有类(父类或基类)继承属性和方法。
  • 多态 (Polymorphism): 不同类的对象能够以各自类特定的方式响应相同的 方法调用。

例如,一个 Car 可以是一个对象。它的状态包括 make(品牌)、model(型号)、color(颜色)和 currentSpeed(当前速度)。它的行为包括 startEngine()(启动引擎)、accelerate()(加速)和 brake()(刹车)。每个 Car 对象都是 Car 类的一个独立实例。

ES6 中引入的 class 关键字提供了一种比 ES6 之前的构造函数和原型操作更直接的方式来创建类。

类可以使用类声明或类表达式来定义。

class ClassName {
constructor(params) {
// 初始化对象属性
}
// 方法
methodName(params) {
// 方法逻辑
}
// Getter 和 Setter
get propertyName() { /* ... */ }
set propertyName(value) { /* ... */ }
// 静态方法
static staticMethodName(params) { /* ... */ }
// 类字段 (ES2022+)
publicProperty = 'initialValue'; // 公共实例字段
#privateProperty = 'secret'; // 私有实例字段
static publicStaticProperty = 'staticValue'; // 公共静态字段
static #privateStaticProperty = 'privateStaticSecret'; // 私有静态字段
}
// 匿名类表达式
const MyClass = class {
constructor(param) {
this.param = param;
}
// ... 其他成员
};
// 命名类表达式
const AnotherClass = class NamedClassExpression {
constructor(param) {
this.param = param;
}
// 名称 'NamedClassExpression' 仅在类体内部有效。
// ... 其他成员
};

类名跟在 class 关键字后面。类名的标识符通常使用帕斯卡命名法 (PascalCase),例如 MyClass。

类定义可以包含:

  • 构造函数 (Constructor): 一个特殊的方法,名称为 constructor,用于创建和初始化对象。一个类只能有一个构造函数。如果您不定义构造函数,将提供一个默认的构造函数。
  • 实例方法 (Instance Methods): 操作类实例的函数。通过 对象名.方法名() 访问。
  • 静态方法 (Static Methods): 属于类本身而不是实例的函数。通过 类名.静态方法名() 访问。
  • Getter 和 Setter: 用于获取和设置对象属性值的特殊方法,允许对访问进行更多控制。
  • 类字段 (Class Fields)(属性): 直接在类体内部定义的变量。它们可以是公共的 (public) 或私有的 (private,使用 # 前缀),可以是实例字段或静态字段。私有字段提供了真正的封装。

注意: 类声明与函数声明不同,不会被提升 (hoisted)。您必须在使用类之前先声明它。

示例: 声明一个带有现代特性的类

Section titled “示例: 声明一个带有现代特性的类”
class Polygon {
// 公共实例字段
type = 'Polygon';
// 私有实例字段
#sides;
constructor(height, width, sides = 4) {
this.height = height;
this.width = width;
this.#sides = sides;
}
// 实例方法
getArea() {
return this.height * this.width;
}
// 私有字段的 Getter
get sides() {
return this.#sides;
}
// 静态方法
static describe() {
return 'A polygon is a closed figure made of straight lines.';
}
}
// 类表达式(匿名)示例
const Square = class extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength, 4); // 调用父类构造函数
this.name = 'Square';
}
// 重写方法
getArea() {
return this.height * this.height; // 或者 this.width * this.width
}
};

要创建类的实例,请使用 new 关键字,后跟类名和构造函数的任何参数。

const objectName = new ClassName(arg1, arg2, ...);

其中:

  • new 关键字负责实例化:它创建一个新对象,将其原型设置为 ClassName.prototype,在构造函数内部将 this 绑定到新对象,并执行构造函数。
  • 如果构造函数带有参数,请传递所需的参数。
const rectangle = new Polygon(10, 12);
console.log(`Rectangle area: ${rectangle.getArea()}`); // Output: Rectangle area: 120
console.log(`Rectangle sides: ${rectangle.sides}`); // Output: Rectangle sides: 4
console.log(Polygon.describe()); // Output: A polygon is a closed figure made of straight lines.

类的实例属性和方法通过对象实例使用点符号 (.) 访问。静态成员通过类名本身访问。

// 访问实例方法
// objectName.methodName();
// 访问实例属性
// console.log(objectName.propertyName);
// 访问静态方法
// ClassName.staticMethodName();
// 访问静态属性
// console.log(ClassName.staticPropertyName);
class Greeter {
#greeting; // 私有字段
constructor(message) {
this.#greeting = message;
}
greet() {
console.log(`${this.#greeting}, welcome!`);
}
static getDefaultGreeting() {
return 'Hello';
}
}
// 创建一个实例
const myGreeter = new Greeter('Good morning');
myGreeter.greet(); // Output: Good morning, welcome!
// 访问静态方法
console.log(Greeter.getDefaultGreeting()); // Output: Hello
// 尝试从外部访问私有字段 (在严格模式下或未正确转换时会引发错误或为 undefined)
// console.log(myGreeter.#greeting); // SyntaxError in strict mode or if not transpiled properly

在 Greeter 类中,this.#greeting 指的是私有实例字段。greet() 方法打印此问候语。静态方法 getDefaultGreeting() 可以直接通过 Greeter 类调用。

instanceof 运算符检查一个对象是否是特定类或继承自该类的类的实例。如果对象的原型链包含构造函数的 prototype 属性,则返回 true。

class Animal {}
class Dog extends Animal {}
const myDog = new Dog();
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true
console.log(myDog instanceof Object); // true (所有对象都继承自 Object)
const myAnimal = new Animal();
console.log(myAnimal instanceof Dog); // false

ES6 类使用 extends 关键字支持继承。继承允许子类(派生类)从父类(基类)派生属性和方法,从而促进代码重用并建立层次关系。

子类继承其父类的所有非私有成员。如果子类有自己的构造函数,它在使用 this 之前必须调用 super()。super() 调用父类的构造函数。

语法:

class ChildClass extends ParentClass {
constructor(paramsForChild, paramsForParent) {
super(paramsForParent); // 调用父类构造函数
// 初始化子类特有的属性
this.childProperty = paramsForChild;
}
childMethod() {
// ...
}
}
class Shape {
constructor(color) {
this.color = color;
}
describe() {
return `A shape of color ${this.color}`;
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color); // 调用 Shape 的构造函数
this.radius = radius;
}
calculateArea() {
return Math.PI * this.radius * this.radius;
}
// 重写 describe 方法
describe() {
return `${super.describe()} with radius ${this.radius}`;
}
}
const redCircle = new Circle('red', 5);
console.log(redCircle.describe()); // Output: A shape of color red with radius 5
console.log(`Area: ${redCircle.calculateArea().toFixed(2)}`); // Output: Area: 78.54

在此示例中,Circle 继承自 Shape。Circle 构造函数通过调用 super('red') 来初始化 color 属性。Circle 也有自己的属性 radius 和方法 calculateArea(),并重写了 describe() 方法。

支持的继承类型:

  • 单继承 (Single Inheritance): 一个类最多只能继承自一个父类。JavaScript/ES6 遵循此模式。
  • 多层继承 (Multilevel Inheritance): 一个类可以从另一个派生类派生。例如,class Square extends Rectangle,其中 class Rectangle extends Shape。

JavaScript 不直接支持多重继承(一个类继承自多个父类)。但是,像 Mixin 或组合(Composition)这样的模式可以达到类似的效果。

class A {
methodA() {
console.log('Method A from class A');
}
}
class B extends A {
methodB() {
console.log('Method B from class B');
}
}
class C extends B {
methodC() {
console.log('Method C from class C');
}
}
const objC = new C();
objC.methodA(); // 继承自 A
objC.methodB(); // 继承自 B
objC.methodC(); // 自身方法

子类可以为其父类中已定义的方法提供特定的实现。这称为方法重写。

class Printer {
print() {
console.log('Default print output.');
}
}
class TextPrinter extends Printer {
// 重写 print 方法
print(text) {
console.log(`Printing text: ${text}`);
}
}
const myTextPrinter = new TextPrinter();
myTextPrinter.print('Hello, ES6+ Classes!'); // 调用 TextPrinter 的 print 方法

super 关键字用于调用父类(基类)的相应方法或访问属性。

  • 在构造函数中: super(...args) 调用父类的构造函数。如果子类继承了另一个类,在使用 this 关键字之前必须调用它。
  • 在实例方法中: super.methodName(...args) 调用父类的方法。super.propertyName 访问父类的属性(通常通过 getter/setter,如果被重写)。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用 Animal 的构造函数
this.breed = breed;
}
speak() {
super.speak(); // 调用 Animal 的 speak 方法
console.log(`${this.name} barks because it's a ${this.breed}.`);
}
}
const myDog = new Dog('Rex', 'German Shepherd');
myDog.speak();
// Output:
// Rex makes a noise.
// Rex barks because it's a German Shepherd.

深入学习: 对于更复杂的 OOP 模式和高级类特性,请参阅官方文档,例如 MDN Web Docs 中关于 JavaScript 类的部分。