类
在 TypeScript 中,类是一种用于创建对象和组织代码的结构。
类的基本结构:
class MyClass {
// 属性
property1: type1;
property2: type2;
// 构造函数
constructor(param1: type1, param2: type2) {
// this 当前的实例
this.property1 = param1;
this.property2 = param2;
}
// 方法
method1(): returnType1 {
// 方法体
}
method2(): returnType2 {
// 方法体
}
}
let my = new myClass();
结构说明:
- 属性:类可以包含多个属性,用于存储数据。属性可以有不同的类型,如字符串、数字、布尔值等。
- 构造函数:构造函数是在创建类的实例时自动调用的特殊方法。它用于初始化类的属性。构造函数可以接受参数,以便在创建实例时传递初始值。
- 方法:类可以包含多个方法,用于执行特定的操作。方法可以接受参数并返回结果。
关键字说明:
class
: 用于声明类。constructor
: 用于定义类的构造函数。constructor
被称为构造函数,构造函数会在创建实例时调用。new myClass();
就是调用了constructor
。
new
: 实例化class
。this
: 用于引用当前类的实例。
在引用任何一个类成员的时候都用了 this
。 它表示我们访问的是类的成员。
继承
在 TypeScript 中,类继承是一种面向对象编程的特性,允许一个类(子类)从另一个类(父类)继承属性和方法。
使用 extends
关键字来实现继承。
示例:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
bark() {
console.log('汪汪汪!');
}
}
let dog = new Dog('Buddy', 'Labrador');
dog.move(10); // Buddy moved 10m.
dog.bark(); // 汪汪汪!
console.log(dog instanceof Animal); // true
在这个例子中:
- 定义父类
Animal
,它具有属性name
、构造函数constructor
和方法move
。 - 定义子类
Dog
,通过extends
关键字继承自Animal
。-
Dog
必须调用super()
,它会执行基类的构造函数。
在子类的构造函数中,必须先调用super()
,然后访问this
的属性或方法。 这个是TypeScript强制执行的一条重要规则。 -
Dog
类有自己的属性breed
。 -
Dog
类还具有自己的方法bark
。
-
这个例子展示了最基本的继承:类从基类中继承了属性和方法。
Dog
是一个 派生类,它派生自 Animal
基类,通过 extends
关键字。 派生类通常被称作 子类,基类通常被称作 超类。
关键字说明
extends
: 用于实现继承。super
: 用于调用父类构造函数或方法。- 如果一个类继承自另一个类,要在子类的构造函数中要访问
this
的属性或方法,必须先调用super()
。因为在子类的构造函数执行之前,父类的构造函数需要先被调用以完成父类部分的初始化工作。如果不先调用super()
就访问this
的属性,可能会导致错误,因为此时子类的实例还没有完全初始化。
- 如果一个类继承自另一个类,要在子类的构造函数中要访问
instanceof
: 用于检查对象是否是某个类的实例。
子类可以重写父类的方法,以提供自己的实现。
示例:
class Bird extends Animal {
constructor(name: string) {
super(name);
}
// 重写move方法
move(distanceInMeters: number) {
console.log(`${this.name} flew ${distanceInMeters}m.`);
}
}
let bird = new Bird('Sparrow');
bird.move(5);
let bird1: Animal = new Bird("magpie");
bird1.move(10);
示例中,Bird
类重写了父类 Animal
的 move
方法,以提供适合鸟类移动的描述。
注意:bird1
被声明为 Animal
类型,但因为它的值是 Bird
,调用 bird1.move(10)
时,它会调用 Bird
里重写的move
方法。
在不同的子类中重写父类的方法,使 方法 根据不同的类而具有不同的功能。
类继承有助于代码的复用和组织,通过继承,可以在子类中扩展和修改父类的功能,从而构建更复杂和有层次的对象模型。
类的访问修饰符
类支持访问修饰符:
public
(公共):默认,可在任何地方访问。private
(私有):只能在类内部访问,子类和类的外部不能访问。protected
(受保护): 在类内部和子类中访问。
公共( public
)
public
是默认的修饰符,如果没有明确指定,类成员就是公共的。
也可以明确的将一个成员标记成 public
。
public
成员可以在类的内部、子类以及类的实例的任何地方被访问和修改。
重写上面的 Animal
类:
class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
私有(private
)
当成员被标记成 private
时,它就不能在声明它的类的外部访问。
private
成员只能在其所属的类内部被访问和修改。在类的外部(包括子类)访问私有成员会导致编译错误。
private
成员在类的实例中不能访问。
示例:
class Animal {
// name 是 Animal 的私有成员
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
bark() {
// Error: Property 'name' is private and only accessible within class 'Animal'.
// 此句报错:属性“name”为私有属性,只能在类“Animal”中访问。
console.log(`The dog's name is ${this.name}`);
}
}
new Animal("Cat").name; // 错误: 属性“name”为私有属性,只能在类“Animal”中访问。
TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。
当比较带有 private
或 protected
成员的类型的时候:如果其中一个类型里包含一个 private
成员,那么只有当另外一个类型中也存在这样一个 private
成员, 并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。 对于 protected
成员也使用这个规则。
示例:
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
// 合法,因为 Rhino 是 Animal 的子类,子类的对象可以赋值给父类类型的变量,这是多态的一种体现。
animal = rhino;
// 错误。不能将类型“Employee”分配给类型“Animal”。类型具有私有属性“name”的单独声明。
// Employee 和 Animal 没有继承关系,并且它们的私有属性 name 是相互独立和不兼容的。
animal = employee;
受保护(protected
)
protected
修饰符与 private
修饰符的行为很相似,但有一点不同: protected
成员在派生类中仍然可以访问。
protected
成员在类的实例中不能访问。
示例:
class Animal {
protected name: string;
constructor(theName: string) { this.name = theName; }
}
class Dog extends Animal {
protected color: string;
constructor(name: string, color: string) {
super(name);
this.color = color
}
bark() {
console.log(`The dog's name is ${this.name}, and color is ${this.color}`);
}
}
let dog = new Dog("Carl", "white");
dog.bark(); // "The dog's name is Carl, and color is white"
new Animal("Cat").name; // 错误: 属性“name”受保护,只能在类“Animal”及其子类中访问。
注意:不能在 Animal
类外使用 name
,可以通过 Dog
类的实例方法访问,因为 Dog
是由 Animal
派生而来的。
构造函数也可以被标记成 protected
。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。
示例:
class Animal {
protected name: string;
// 把构造函数标记为protected
protected constructor(theName: string) { this.name = theName; }
}
// Dog 能继承 Animal
// Dog 调用 `super()`,执行基类 Animal 受保护的 构造函数。
class Dog extends Animal {
color: string;
constructor(name: string, color: string) {
super(name);
this.color = color
}
bark() {
console.log(`The dog's name is ${this.name}, and color is ${this.color}`);
}
}
let dog = new Dog("Carl", "white");
dog.bark(); // "The dog's name is Carl, and color is white"
new Animal("Cat").name; // 错误: 类“Animal”的构造函数是受保护的,仅可在类声明中访问。
readonly修饰符
readonly
成员只能读取,不能被重新赋值。
readonly
成员必须在声明时或构造函数里被初始化。
class Point {
readonly x: number;
readonly y: number = 10;
constructor(x: number, y?: number) {
this.x = x;
this.y = y ?? this.y;
}
showPoint() {
console.log(`x: ${this.x}, y: ${this.y}`)
}
}
let p = new Point(10, 20);
p.showPoint();
let p1 = new Point(50);
p1.showPoint();
p1.x = 30; // Error: 无法为“x”赋值,因为它是只读属性。
参数属性
在 TypeScript 中,类的参数属性是一种简洁的方式来同时声明和初始化类的属性。
示例:
class Point {
constructor(readonly x: number, readonly y?: number) {}
}
在构造函数里仅使用 readonly x: number, readonly y?: number
参数来创建和初始化 x
、y
。
把声明和赋值合并在一个地方。
参数属性通过给构造函数参数前面添加一个访问限定符来声明。比如:
class Animal {
constructor(protected name: string;) {}
}
class Animal {
constructor(private name: string;) {}
}
getters/setters
TypeScript支持通过 getters
(获取器)和 setters
(设置器)来截取对对象成员的访问。
示例:没有使用getters/setters
class User {
name: string;
}
let user = new User();
user.name = "Rigo";
console.log(user.name); // "Rigo"
user.name = "张三";
console.log(user.name); // "张三"
可以随意的设置 name
。
把 Animal
类改写成使用 get
和 set
: set
方法中,验证用户输入的密码,密码正确才可以修改name
。使用get
方法获取 name
。
class User {
private _name: string = "";
get name(): string {
return this._name;
}
set name(newName: string) {
let pwd = prompt("请输入密码", "password");
if(pwd === "password") {
this._name = newName;
}else {
console.log("密码错误!");
}
}
}
let user = new User();
user.name = "张三";
console.log(user.name); // 张三
在 User
类中,private _name: string
声明变量会报错:Property ‘_name’ has no initializer and is not definitely assigned in the constructor.
这个错误是因为 TypeScript 在严格的类型检查模式下,要求非 readonly
且类型不可为 null
或 undefined
的属性必须在构造函数中被初始化,或者具有明确的初始值。
private _name: string = "";
为 _name
属性添加初始值 ""
时,满足了 TypeScript 对于属性初始化的要求。TypeScript 就能够确定在对象创建时该属性有一个明确的初始状态,从而避免了可能出现未初始化就被使用的情况。
注意:只有 get
且没有 set
的存取器自动被推断为 readonly
。 因为没有 set
提供修改值的方法。
静态属性
在 TypeScript 中,静态属性是属于类本身而不是类的实例的属性。
类的实例成员:仅当类被实例化的时候才会被初始化的属性。
类的静态成员:属于类本身而不是类的实例的属性。
每个实例通过 类名.静态属性
访问静态属性。比如示例中的MyClass.staticProperty
。
示例:
class MyClass {
// 静态属性
static staticProperty: number = 10;
// 实例方法
instanceMethod() {
console.log(`实例化后访问静态属性值:${MyClass.staticProperty}`);
}
}
// 直接通过类名访问静态属性
console.log(MyClass.staticProperty);
// 实例化后,才能通过实例 instance 访问 instanceMethod()
let instance = new MyClass();
instance.instanceMethod();
let instance1 = new MyClass();
instance1.instanceMethod();
在这个示例中,无论创建多少个 instance
的实例,staticProperty
的值都是共享且不变的。
静态属性通常用于存储与类相关的全局数据或共享状态,并且在类的所有实例之间是共享的。
抽象类
抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 抽象类可以包含成员的实现细节。
abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法。
// 定义抽象类 Animal
abstract class Animal {
// 抽象方法 makeSound
abstract makeSound(): void;
// 非抽象方法 move
move() {
console.log('The animal is moving.');
}
}
抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
抽象方法只定义方法签名。
抽象方法必须包含 abstract
关键字并且可以包含访问修饰符。
abstract class Animal {
abstract makeSound(): void; // 必须在派生类中实现
move() {
console.log("The animal is moving.");
}
}
class Dog extends Animal {
makeSound() {
console.log("Woof!");
}
jump() {
console.log("dog jump")
}
}
let dog = new Dog();
dog.makeSound();
dog.move();
dog.jump();
let cat = new Animal(); // Error: 无法创建抽象类的实例。
由于 Animal
是抽象类,不能直接创建 Animal
的实例。
允许创建一个对抽象类型的引用:
abstract class Animal {
abstract makeSound(): void; // 必须在派生类中实现
move() {
console.log("The animal is moving.");
}
}
class Dog extends Animal {
makeSound() {
console.log("Woof!");
}
}
class Cat extends Animal {
makeSound() {
console.log("喵~");
}
}
// operateAnimal 函数接受一个类型为 Animal 的参数。
function operateAnimal(animal: Animal) {
animal.makeSound();
animal.move();
}
let dog = new Dog();
let cat = new Cat();
operateAnimal(dog);
operateAnimal(cat);
operateAnimal
函数接受一个类型为 Animal
的参数。
当我们调用 operateAnimal(dog)
和 operateAnimal(cat)
时,参数传递的实际对象分别是 dog
和 cat
的实例。在函数内部,我们可以通过 animal
这个抽象类型的引用统一地调用它们共有的方法。
把类当做接口使用
类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以能在允许使用接口的地方使用类。
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
构造函数
在 TypeScript 中,构造函数是类中的一个特殊方法,用于在创建类的实例时进行初始化操作。
构造函数的基本结构:
class MyClass {
constructor(param1: type1, param2: type2) {
// 初始化属性
this.property1 = param1;
this.property2 = param2;
}
}
构造函数通常接受一些参数,这些参数可以用于初始化类的实例属性。在构造函数内部,可以使用 this
关键字来引用正在创建的实例,并为其属性赋值。
构造函数的作用
- 初始化实例属性:构造函数的主要作用是在创建类的实例时,为实例的属性赋予初始值。这确保了每个实例都以特定的状态开始。
- 执行其他初始化逻辑:除了初始化属性,构造函数还可以执行其他初始化逻辑,如调用其他方法、进行验证等。
示例:
class Person {
name: string;
age: number;
// 这是构造函数
constructor(name: string, age: number) {
console.log('构造函数执行了!')
this.name = name;
this.age = age;
if(this.age <= 0) {
throw Error('年龄不能小于等于0!');
}
}
}
let person = new Person('Alice', 25);
console.log('打印person:', person);
let person1 = new Person('John', -1);
constructor
被称为构造函数,构造函数会在创建实例时调用。
执行 new Person(...)
就是在调用Person
类的constructor
。
构造函数与继承的关系
- 子类构造函数中的
super ()
:当一个类继承自另一个类时,子类的构造函数必须先调用super()
来调用父类的构造函数,以确保父类的初始化工作先完成。 - 传递参数给父类构造函数:在子类构造函数中,可以通过 super() 传递参数给父类的构造函数,以满足父类的初始化需求。
示例:
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
constructor(name: string, public breed: string) {
// 必须先调用super()
super(name);
// 然后才能访问this 的属性
this.breed = breed;
}
}
const dog = new Dog('Buddy', 'Labrador');
console.log(dog.name); // Buddy
console.log(dog.breed); // Labrador