TypeScript 中的 class 是基于 ECMAScript 2015 (ES6) 引入的类语法的扩展。在 TypeScript 中,类是一种用于创建对象的蓝图或模板。它封装了对象的属性和方法,让代码更加模块化和可重用。
类的基本结构
cpp
class MyClass {
// 类的属性(字段)
myProperty: string;
// 构造函数
constructor(initialProperty: string) {
this.myProperty = initialProperty;
}
// 类的方法
myMethod(): void {
console.log(this.myProperty);
}
}
// 创建 MyClass 的一个实例
const myInstance = new MyClass("Hello, World!");
// 调用实例的方法
myInstance.myMethod(); // 输出: Hello, World!
这段代码展示了在TypeScript中如何定义一个类(MyClass),创建这个类的一个实例(myInstance),并调用该实例的一个方法(myMethod)。
- 类的属性:
myProperty
是一个字符串类型的属性,它用于存储类的实例的某个状态或数据。 - 构造函数:
constructor(initialProperty: string)
是一个特殊的方法,用于在创建类的实例时初始化对象。这里,它接受一个字符串参数initialProperty
,并将其值赋给类的myProperty
属性。注意,在TypeScript中,即使你没有显式地定义构造函数,编译器也会为你生成一个空的构造函数。 - 类的方法:
myMethod()
是一个没有参数和返回值(返回类型为 void)的方法。它使用console.log
输出myProperty
属性的值。 - 类的实例: 通过
new
关键字和类名MyClass
创建了MyClass
类的一个新实例,并将其赋值给常量myInstance
。在创建实例时,传递了字符串"Hello, World!"
作为参数给构造函数,这个值被用于初始化myInstance
实例的myProperty
属性。 - 实例的方法: 调用
myInstance
实例的myMethod
方法。由于myMethod
方法内部使用console.log
输出了myProperty
属性的值,而myProperty
属性已经被初始化为"Hello, World!"
,因此控制台将显示输出Hello, World!
。
this 关键字有一个特殊的含义,它指的是当前对象的上下文(context)。在类的方法中,当方法作为实例方法被调用时(即通过实例对象调用),this 会自动指向调用它的那个实例。
类的继承
在TypeScript中,类的继承是一个非常重要的概念,它允许你基于一个或多个已存在的类来创建新的类。继承使得代码的重用和扩展变得更加容易。
cpp
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.log('Woof! Woof!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
这个例子展示了最基本的继承:类从基类中继承了属性和方法。 这里, Dog是一个 派生类,它派生自 Animal
基类,通过 extends
关键字。 派生类通常被称作 子类,基类通常被称作超类。 因为 Dog
继承了 Animal
的功能,因此我们可以创建一个 Dog
的实例,它能够 bark()
和 move()
。
cpp
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
这个例子展示了一些上面没有提到的特性。 这一次,我们使用 extends
关键字创建了 Animal
的两个子类: Horse
和 Snake
。 与前一个例子的不同点是,派生类包含了一个构造函数,它必须调用 super(),它会执行基类的构造函数。 而且,在构造函数里访问 this的属性之前,我们一定要调用 super()。 这个是TypeScript强制执行的一条重要规则。
访问修饰符
public:
公开的,谁都能用(默认public) private:
当成员被标记成 private时,它就不能在声明它的类的外部访问。
cpp
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // 错误: 'name' 是私有的.
protected:
protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问。
cpp
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name)
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 错误
注意,我们不能在 Person类外使用 name,但是我们仍然可以通过 Employee类的实例方法访问,因为 Employee是由 Person派生而来的。 构造函数也可以被标记成 protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。
只读修饰符
只能读不能写,需要注意的时,即使是readonly的东西,在初始化之前是可以写,即在constructor中可以初始化或更改。
cpp
class Person {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
const person = new Person("Alice");
person.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property.
上面的代码可以更改为参数属性,将readonly属性与构造函数参数结合使用,你可以直接在构造函数参数上应用readonly修饰符,这样就不需要在构造函数体内显式地赋值了。
cpp
class Person {
constructor(public readonly name: string) {}
}
const person = new Person("Alice");
person.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property.
存取器
TypeScript(以及它的基础JavaScript)中,存取器(Accessors)是一对特殊的方法,用于封装对对象属性的读取和写入操作。存取器由get和set关键字定义,分别用于获取(读取)和设置(写入)属性值。它们可以让你在不暴露实际数据成员的情况下控制对数据的访问。
cpp
class MyClass {
private _myValue: number;
get myValue(): number {
return this._myValue;
}
set myValue(value: number) {
if (value >= 0) {
this._myValue = value;
} else {
throw new Error('Value must be non-negative.');
}
}
}
在这个例子中,myValue存取器允许你读取和设置_myValue的值,但是设置时会检查值是否非负。这提供了额外的验证逻辑,而不会让外部代码直接访问或修改_myValue。 使用存取器 你可以像访问普通属性一样使用存取器:
cpp
const obj = new MyClass();
obj.myValue = 10; // 设置值
console.log(obj.myValue); // 获取值,输出: 10
如果尝试设置一个非法值,将会抛出错误:
cpp
obj.myValue = -5; // 抛出错误: Value must be non-negative.
静态属性
静态属性(Static Properties)是属于类本身的属性,而不是类的实例。这意味着无论创建多少个类的实例,静态属性都只有一个副本,存储在类的构造函数原型上,而不是在每个实例上。
cpp
class MyClass {
static myStaticProperty: string = "Hello, World!";
}
console.log(MyClass.myStaticProperty); // 输出: "Hello, World!"
class MyClass {
static myStaticMethod() {
return "Called static method.";
}
}
console.log(MyClass.myStaticMethod()); // 输出: "Called static method."
静态属性和方法通常用于以下场景:
- 常量或配置信息:如果你有一些常量或配置信息,这些信息对于所有实例都是相同的,那么使用静态属性会很有意义。
- 实用工具方法:一些不依赖于实例状态的方法,可以作为静态方法提供,这样可以直接调用,无需创建实例。
- 静态属性可以在类定义中初始化,也可以在类之外初始化:
cpp
class MyClass {
static myStaticProperty;
}
MyClass.myStaticProperty = "Initialized later";
console.log(MyClass.myStaticProperty); // 输出: "Initialized later"
抽象类
抽象类(Abstract Classes)是一种特殊的类,它不能被实例化,主要用于作为其他类的基础类。抽象类中可以包含抽象方法和抽象属性,这些抽象成员必须在继承自抽象类的子类中实现。
cpp
abstract class Animal {
abstract makeSound(): void; // 抽象方法,必须在子类中实现
speak(): void {
this.makeSound();
}
}
class Dog extends Animal {
makeSound(): void {
console.log("Woof!");
}
}
const dog = new Dog();
dog.speak(); // 输出: "Woof!"
注意事项
- 抽象类不能被直接实例化。试图创建抽象类的实例会导致编译错误。
- 抽象方法和抽象属性必须在所有非抽象的派生类中实现。
- 抽象类可以包含非抽象方法和属性,这些可以被继承并在派生类中使用。