ES6 - 类
ES6+ 现代 JavaScript 类
Section titled “ES6+ 现代 JavaScript 类”面向对象编程(Object-Oriented Programming, OOP)是一种使用对象及其交互来设计和编程应用程序的范式。它将软件建模为对象的集合,这些对象是类的实例,通过方法进行通信。ES6 引入了 class 语法,它简化了对象的创建和继承的实现,为习惯于基于类的语言的开发人员提供了一种更清晰、更熟悉的结构。这种语法主要是 JavaScript 现有基于原型继承的语法糖(syntactic sugar)。
JavaScript 中的核心 OOP 概念
Section titled “JavaScript 中的核心 OOP 概念”让我们回顾一下基本的 OOP 概念:
- 对象 (Object): 类的一个实例,代表一个真实世界的实体或抽象概念。每个对象都有:
- 状态 (State): 由其属性(数据成员或属性)描述。
- 行为 (Behavior): 由其方法(与对象关联的函数)定义。
- 身份 (Identity): 一个唯一的引用,用于将其与其他对象区分开。
- 类 (Class): 创建对象的蓝图或模板。它封装了数据(属性)和函数(方法),这些数据和函数定义了对象。
- 方法 (Method): 与类或对象关联的函数,定义其行为并实现通信。
- 封装 (Encapsulation): 将数据(属性)和操作这些数据的方法捆绑到单个单元(类)中。ES6+ 类通过公共和私有成员支持这一点。
- 继承 (Inheritance): 允许一个新类(子类或派生类)从现有类(父类或基类)继承属性和方法。
- 多态 (Polymorphism): 不同类的对象能够以各自类特定的方式响应相同的 方法调用。
例如,一个 Car 可以是一个对象。它的状态包括 make(品牌)、model(型号)、color(颜色)和 currentSpeed(当前速度)。它的行为包括 startEngine()(启动引擎)、accelerate()(加速)和 brake()(刹车)。每个 Car 对象都是 Car 类的一个独立实例。
ES6 中引入的 class 关键字提供了一种比 ES6 之前的构造函数和原型操作更直接的方式来创建类。
类可以使用类声明或类表达式来定义。
语法: 类声明
Section titled “语法: 类声明”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'; // 私有静态字段}语法: 类表达式
Section titled “语法: 类表达式”// 匿名类表达式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 }};创建对象 (实例化)
Section titled “创建对象 (实例化)”要创建类的实例,请使用 new 关键字,后跟类名和构造函数的任何参数。
const objectName = new ClassName(arg1, arg2, ...);其中:
new关键字负责实例化:它创建一个新对象,将其原型设置为ClassName.prototype,在构造函数内部将this绑定到新对象,并执行构造函数。- 如果构造函数带有参数,请传递所需的参数。
示例: 实例化一个类
Section titled “示例: 实例化一个类”const rectangle = new Polygon(10, 12);console.log(`Rectangle area: ${rectangle.getArea()}`); // Output: Rectangle area: 120console.log(`Rectangle sides: ${rectangle.sides}`); // Output: Rectangle sides: 4console.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);示例: 综合运用
Section titled “示例: 综合运用”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 运算符
Section titled “instanceof 运算符”instanceof 运算符检查一个对象是否是特定类或继承自该类的类的实例。如果对象的原型链包含构造函数的 prototype 属性,则返回 true。
class Animal {}class Dog extends Animal {}
const myDog = new Dog();
console.log(myDog instanceof Dog); // trueconsole.log(myDog instanceof Animal); // trueconsole.log(myDog instanceof Object); // true (所有对象都继承自 Object)
const myAnimal = new Animal();console.log(myAnimal instanceof Dog); // falseES6 类使用 extends 关键字支持继承。继承允许子类(派生类)从父类(基类)派生属性和方法,从而促进代码重用并建立层次关系。
子类继承其父类的所有非私有成员。如果子类有自己的构造函数,它在使用 this 之前必须调用 super()。super() 调用父类的构造函数。
语法:
class ChildClass extends ParentClass { constructor(paramsForChild, paramsForParent) { super(paramsForParent); // 调用父类构造函数 // 初始化子类特有的属性 this.childProperty = paramsForChild; }
childMethod() { // ... }}示例: 类继承
Section titled “示例: 类继承”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 5console.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)这样的模式可以达到类似的效果。
示例: 多层继承
Section titled “示例: 多层继承”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(); // 继承自 AobjC.methodB(); // 继承自 BobjC.methodC(); // 自身方法方法重写 (Method Overriding)
Section titled “方法重写 (Method Overriding)”子类可以为其父类中已定义的方法提供特定的实现。这称为方法重写。
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 关键字
Section titled “super 关键字”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 类的部分。