interface和类

2024/4/8 TypeScript

# interface

​ interface是对象的模板,可以看作是一种类型约定,中文译为接口。使用了某个模板的对象,就拥有了指定的类型结构。方括号运算符可以取出interface某个属性的类型。

赋值时,变量的形状必须和接口的形状保持一致。定义的变量比接口少一些属性、多一些属性都是不允许的。

interface Person {
  firstName: string;
  lastName: string;
  age: number;
} // 任何实现这个接口的对象,都必须部署这三个属性,并且必须符合规定的类型
const p:Person = { // 变量p的类型就是接口Person,所以必须符合Person指定的结构
  firstName: 'John',
  lastName: 'Smith',
  age: 25
}; // 实现该接口很简单,只要指定它作为对象的类型即可
type A = Person['age']; // number
1
2
3
4
5
6
7
8
9
10
11

​ interface可以表示对象的各种语法,它的成员有5种形式:

  • 对象属性
    • 分别使用冒号指定每个属性的类型。
    • 属性之间使用分号或逗号分隔,最后一个属性结尾的分号或逗号可以省略。
    • 如果属性是可选的,就在属性名后面加一个问号。
    • 如果属性是只读的,需要加上readonly修饰符。
interface Point {
  x?: string;
  readonly y: string;
  [prop: string]: number;
}
1
2
3
4
5
  • 对象的属性索引
    • 属性索引共有string、number和symbol三种类型。
    • 一个接口中最多只能定义一个字符串索引。字符串索引会约束该类型中所有名字为字符串的属性。
    • 属性的数值索引,其实是指定数组的类型。
    • 一个接口中最多只能定义一个数值索引。数值索引会约束所有名称为数值的属性。
    • 如果一个interface同时定义了字符串索引和数值索引,那么数值索引必须服从于字符串索引。因为在JavaScript中,数值属性名最终是自动转换成字符串属性名。
interface C {
  [prop: number]: string;
}
const obj:C = ['a', 'b', 'c']; // 属性名的类型是数值,所以可以用数组对变量obj赋值
interface A {
  [prop: string]: number;
  [prop: number]: string; // 报错。数值索引的属性值类型与字符串索引不一致,就会报错
}
interface B {
  [prop: string]: number;
  [prop: number]: number; // 正确。数值索引必须兼容字符串索引的类型声明
}
1
2
3
4
5
6
7
8
9
10
11
12
  • 对象方法
    • 对象的方法共有三种写法。属性名可以采用表达式。
    • 类型方法可以重载。interface里面的函数重载不需要给出实现。但是,由于对象内部定义方法时,无法使用函数重载的语法,所以需要额外在对象外部给出函数方法的实现。
// 写法一
interface A {
  f(x: boolean): string;
}
// 写法二
interface B {
  f: (x: boolean) => string;
}
// 写法三
interface C {
  f: { (x: boolean): string };
}
// 属性名可以采用表达式,所以下面的写法也是可以的。
const f = 'f';
interface A {
  [f](x: boolean): string;
}
interface A {
  f(): number;
  f(x: boolean): boolean;
  f(x: string, y: string): string;
} // 函数重载,不需要给出实现,需要额外在对象外部给出函数方法的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 函数
    • interface 也可以用来声明独立的函数。
interface Add {
  (x:number, y:number): number;
} // 声明了一个函数类型
const myAdd:Add = (x,y) => x + y;
1
2
3
4
  • 构造函数
    • interface 内部可以使用new关键字,表示构造函数。
interface ErrorConstructor {
  new (message?: string): Error; // 内部有new命令,表示它是一个构造函数
}
1
2
3
  • 有时我们希望一个接口允许有任意的属性,可以使用如下方式。
interface Person {
    name: string;
    age?: number;
    [propName: string]: any; 
} // 使用 [propName: string] 定义了任意属性取 string 类型的值。
interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
} // 一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
interface Person {
    name: string;
    age?: number;
    [propName: string]: string | number;
} // 如果接口中有多个类型的属性,则可以在任意属性中使用联合类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# interface 的继承

# interface 继承 interface

​ interface可以使用extends关键字,继承其他interface。extends关键字会从继承的接口里面拷贝属性类型。这样就不必书写重复的属性。

​ interface 允许多重继承。多重接口继承,实际上相当于多个父接口的合并。多重继承时,如果多个父接口存在同名属性,那么这些同名属性不能有类型冲突,否则会报错。

interface Style {
  color: string;
}
interface Shape {
  name: string;
}
interface Circle extends Style, Shape {
  radius: number;
} // Circle同时继承了Style和Shape,所以拥有三个属性color、name和radius
1
2
3
4
5
6
7
8
9

​ 如果子接口与父接口存在同名属性,那么子接口的属性会覆盖父接口的属性。子接口与父接口的同名属性必须是类型兼容的,不能有冲突,否则会报错。

interface Foo {
  id: string;
}
interface Bar {
  id: number;
}
// 报错
interface Baz extends Foo, Bar {
  type: string;
} // Baz同时继承了Foo和Bar,但是后两者的同名属性id有类型冲突
1
2
3
4
5
6
7
8
9
10

# interface 继承 type

​ interface可以继承type命令定义的对象类型。如果type命令定义的类型不是对象,interface就无法继承。

type Country = {
  name: string;
  capital: string;
}
interface CountryWithPop extends Country {
  population: number;
} // CountryWithPop继承了type命令定义的Country对象,并且新增了一个population属性
1
2
3
4
5
6
7

# interface 继承 class

​ interface还可以继承class,即继承该类的所有成员。某些类拥有私有成员和保护成员,interface可以继承这样的类,但无法用于对象,意义不大。

class A { // A有私有成员和保护成员
  private x: string = '';
  protected y: string = '';
}
interface B extends A { // B继承了A,但无法用于对象,因为对象不能实现这些成员
  z: number
}
// 报错
const b:B = { /* ... */ }
// 报错
class C implements B { //致B只能用于其他class
  // ...
} // 这时其他class与A之间不构成父类和子类的关系,使得x与y无法部署
1
2
3
4
5
6
7
8
9
10
11
12
13

# 接口合并

​ 多个同名接口会合并成一个接口。JavaScript开发者常常对全局对象或者外部库,添加自己的属性和方法。只要使用interface给出这些自定义属性和方法的类型,就能自动跟原始的interface合并,使得扩展外部类型非常方便。

​ 同名接口合并时,同一个属性如果有多个类型声明,彼此不能有类型冲突。

​ 同名接口合并时,如果同名方法有不同的类型声明,那么会发生函数重载。而且,后面的定义比前面的定义具有更高的优先级。但是,如果有一个参数是字面量类型,字面量类型的优先级会排到最前面。

​ 如果两个interface组成的联合类型存在同名属性,那么该属性的类型也是联合类型。

接口的合并:接口中的属性在合并时会简单的合并到一个接口中,并的属性的类型必须是唯一的。

# interface 与 type 的异同

​ interface命令与type命令作用类似,都可以表示对象类型。很多对象类型既可用interface表示,也可用type表示。而且,两者往往可以换用,几乎所有的 interface 命令都可以改写为 type 命令。

​ 它们的相似之处,表现在都能为对象类型起名。

type Country = {
  name: string;
  capital: string;
}
interface Country {
  name: string;
  capital: string;
}
1
2
3
4
5
6
7
8

class命令也有类似作用,通过定义一个类,同时定义一个对象类型。但是,它会创造一个值,编译后依然存在。如果只是单纯想要一个类型,应该使用type或interface。

​ interface 与 type 的区别有下面几点:

  • type能够表示非对象类型,而interface只能表示对象类型(包括数组、函数等)。
  • interface可以继承其他类型,type不支持继承。
    • 继承的主要作用是添加属性,type定义的对象类型如果想要添加属性,只能使用&运算符,重新定义一个类型。(&运算符表示同时具备两个类型的特征,可以起到两个对象类型合并的作用。)
    • interface添加属性,采用的是继承的写法。继承时,type 和 interface 是可以换用的。interface 可以继承 type。type 也可以继承 interface。
  • 同名interface会自动合并,同名type则会报错。即TS不允许使用type多次定义同一个类型。
  • interface不能包含属性映射(mapping),type可以。
  • this关键字只能用于interface。
  • type可以扩展原始数据类型,interface 不行。
  • interface无法表达某些复杂类型(比如交叉类型和联合类型),但是type可以。
type Foo = { x: number; };
interface Bar extends Foo { // interface 可以继承 type
  y: number;
}
interface Foo {
  x: number;
}
type Bar = Foo & { y: number; }; // type 也可以继承 interface
1
2
3
4
5
6
7
8

如果有复杂的类型运算,那么没有其他选择只能使用type;一般情况下,interface灵活性比较高,便于扩充类型或自动合并,建议优先使用。

#

​ 类是面向对象编程的基本构件,封装了属性和方法,TypeScript 给予了全面支持。

​ 这里对类相关的概念做一个简单的介绍。 类(Class):定义了一件事物的抽象特点,包含它的属性和方法 对象(Object):类的实例,通过 new 生成 面向对象(OOP)的三大特性:封装、继承、多态 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat 存取器(getter & setter):用以改变属性的读取和赋值行为 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口

# 属性的类型

​ 类的属性可以在顶层声明,也可以在构造方法内部声明。对于顶层声明的属性,可以在声明时同时给出类型。

​ 如果不给出类型,TypeScript 会认为是any。如果声明时给出初值,可以不写类型,TypeScript 会自行推断属性的类型。

​ TypeScript 有一个配置项strictPropertyInitialization,只要打开(默认是打开的),就会检查属性是否设置了初值,如果没有就报错。

​ 如果类的顶层属性不赋值,就会报错。如果不希望出现报错,可以使用非空断言。就是说,在属性名后面添加感叹号,表示这两个属性肯定不会为空,TypeScript就不报错了。

class Point {
  x!: number;
  y!: number;
}
// 打开 strictPropertyInitialization
class Point {
  x: number; // 报错
  y: number; // 报错
}
1
2
3
4
5
6
7
8
9

# readonly 修饰符

​ 属性名前面加上readonly修饰符,就表示该属性是只读的。实例对象不能修改这个属性。

​ readonly 属性的初始值,可以写在顶层属性,也可以写在构造方法里面。

​ 构造方法内部设置只读属性的初值、修改只读属性的值,都是可以的。或者说,如果两个地方都设置了只读属性的值,以构造方法为准。在其他方法修改只读属性都会报错。

​ 如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面。

修饰符和readonly还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁。readonly只允许出现在属性声明或索引签名或构造函数中。

# 方法的类型

​ 类的方法就是普通函数,类型声明方式与函数一致。

​ 类的方法跟普通函数一样,可以使用参数默认值,以及函数重载。

​ 构造方法可以接受一个参数,也可以接受两个参数,采用函数重载进行类型声明。另外,构造方法不能声明返回值类型,否则报错,因为它总是返回实例对象。

# 存取器方法

​ 存取器(accessor)是特殊的类方法,包括取值器(getter)和存值器(setter)两种方法。它们用于读写某个属性,取值器用来读取属性,存值器用来写入属性。

class C {
  _name = '';
  get name() { // 取值器,其中get是关键词,name是属性名
    return this._name;
  } // 外部读取name属性时,实例对象会自动调用这个方法,该方法的返回值就是name属性的值
  set name(value) { // 存值器,其中set是关键词,name是属性名
    this._name = value;
  } // 外部写入name属性时,实例对象会自动调用这个方法,并将所赋的值作为函数参数传入
} 
1
2
3
4
5
6
7
8
9

​ TypeScript 对存取器有以下规则。

  • 如果某个属性只有get方法,没有set方法,那么该属性自动成为只读属性。
  • TS 5.1版之前,set方法的参数类型,必须兼容get方法的返回值类型,否则报错。TS5.1 版做出了改变,现在两者可以不兼容。
  • get方法与set方法的可访问性必须一致,要么都为公开方法,要么都为私有方法。

# 属性索引

​ 类允许定义属性索引。

class MyClass {
  [s:string]: boolean |
    ((s:string) => boolean); //所有属性名类型为字符串的属性,属性值要么是布尔值,要么是返回布尔值的函数
  get(s:string) {
    return this[s] as boolean;
  }
}
1
2
3
4
5
6
7

​ 注意,由于类的方法是一种特殊属性(属性值为函数的属性),所以属性索引的类型定义也涵盖了方法。如果一个对象同时定义了属性索引和方法,那么前者必须包含后者的类型。

class MyClass {
  [s:string]: boolean | (() => boolean);
  f() {
    return true;
  }
}
1
2
3
4
5
6

​ 属性存取器视同属性。

class MyClass {
  [s:string]: boolean; // 属性索引虽然没有涉及方法类型,但是不会报错
  get isInstance() { // 读取器虽然是一个函数方法,但是视同属性
    return true;
  }
}
1
2
3
4
5
6

# 类的 interface 接口

​ 有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口,用 implements 关键字来实现。

# implements 关键字

​ interface 接口或 type 别名,可以用对象的形式,为 class 指定一组检查条件。然后,类使用 implements 关键字,表示当前类满足这些外部类型条件的限制。

​ interface只是指定检查条件,如果不满足这些条件就会报错。它并不能代替class自身的类型声明。比如,类B实现了接口A,但A并不能代替B的类型声明,B类依然需要声明参数的类型,需要声明可选属性。

​ 类可以定义接口没有声明的方法和属性。表示除了满足接口给出的条件,类还有额外的条件。

​ implements关键字后面,不仅可以是接口,也可以是另一个类。这时,后面的类将被当作接口。在接口继承类的时候,也只会继承它的实例属性和实例方法。

​ 注意,interface描述的是类的对外接口,也就是实例的公开属性和公开方法,不能定义私有的属性和方法。这是因为TypeScript设计者认为,私有属性是类的内部实现,接口作为模板,不应该涉及类的内部代码写法。

# 实现多个接口

​ 一个类可以实现多个接口(其实是接受多重限制),每个接口之间使用逗号分隔。但是,同时实现多个接口并不是一个好的写法,容易使得代码难以管理,可以使用两种方法替代。第一种方法是类的继承。第二种方法是接口的继承。在 TypeScript 中,接口与接口之间可以是继承关系。

class Car implements MotorVehicle {
}
class SecretCar extends Car implements Flyable, Swimmable { // 类的继承
}
interface A {
  a:number;
}
interface B extends A { // 接口的继承
  b:number;
}
1
2
3
4
5
6
7
8
9
10

​ 注意,发生多重实现时(即一个接口同时实现多个接口),不同接口不能有互相冲突的属性。

# 类与接口的合并

​ TypeScript不允许两个同名的类,但是如果一个类和一个接口同名,那么接口会被合并进类。注意,合并进类的非空属性,如果在赋值之前读取,会返回undefined。

类的合并:类的合并与接口的合并规则一致。

# Class 类型

# 实例类型

​ TypeScript的类本身就是一种类型,但是它代表该类的实例类型,而不是class的自身类型。

​ 对于引用实例对象的变量来说,既可以声明类型为 Class,也可以声明类型为 Interface,因为两者都代表实例对象的类型。

interface MotorVehicle {
}
class Car implements MotorVehicle {
}
// 写法一
const c1:Car = new Car();
// 写法二
const c2:MotorVehicle = new Car();
1
2
3
4
5
6
7
8

​ 作为类型使用时,类名只能表示实例的类型,不能表示类的自身类型。

​ 由于类名作为类型使用,实际上代表一个对象,因此可以把类看作为对象类型起名。事实上,TypeScript有三种方法可以为对象类型起名:type、interface 和 class。

# 类的自身类型

​ 类的自身类型就是一个构造函数,可以单独定义一个接口来表示。要获得一个类的自身类型,一个简便的方法就是使用 typeof 运算符。

​ 类只是构造函数的一种语法糖,本质上是构造函数的另一种写法。所以,类的自身类型可以写成构造函数的形式。构造函数也可以写成对象形式。

​ 可以把构造函数提取出来,单独定义一个接口,这样可以大大提高代码的通用性。

interface PointConstructor {
  new(x:number, y:number):Point;
}
function createPoint(
  PointClass: PointConstructor,
  x: number,
  y: number
):Point {
  return new PointClass(x, y);
}
1
2
3
4
5
6
7
8
9
10

# 结构类型原则

​ Class也遵循结构类型原则。一个对象只要满足 Class 的实例结构,就跟该 Class 属于同一个类型。

​ 如果两个类的实例结构相同,那么这两个类就是兼容的,可以用在对方的使用场合。

​ 只要 A 类具有 B 类的结构,哪怕还有额外的属性和方法,TypeScript也认为 A 兼容 B 的类型。

​ 不仅是类,如果某个对象跟某个 class 的实例结构相同,TypeScript也认为两者的类型相同。由于这种情况,运算符instanceof不适用于判断某个对象是否跟某个 class 属于同一类型。

​ 空类不包含任何成员,任何其他类都可以看作与空类结构相同。因此,凡是类型为空类的地方,所有类(包括对象)都可以使用。

​ 注意,确定两个类的兼容关系时,只检查实例成员,不考虑静态成员和构造方法。

​ 如果类中存在私有成员(private)或保护成员(protected),那么确定兼容关系时,TypeScript 要求私有成员和保护成员来自同一个类,这意味着两个类需要存在继承关系。

# 类的继承

​ 类(这里又称子类)可以使用extends关键字继承另一个类(这里又称基类)的所有属性和方法。一般来讲,一个类只能继承自另一个类。

​ 根据结构类型原则,子类也可以用于类型为基类的场合。子类可以覆盖基类的同名方法。

​ 使用super关键字指代基类是常见做法。

​ 子类的同名方法不能与基类的类型定义相冲突。

​ 如果基类包括保护成员(protected修饰符),子类可以将该成员的可访问性设置为公开(public修饰符),也可以保持保护成员不变,但是不能改用私有成员(private修饰符)。

​ extends关键字后面不一定是类名,可以是一个表达式,只要它的类型是构造函数就可以了。

​ 对于那些只设置了类型、没有初值的顶层属性,有一个细节需要注意。没有设置初值,代码在不同的编译设置下编译结果不一样。解决方法就是使用declare命令,去声明顶层成员的类型,告诉 TS这些成员的赋值由基类实现。

# 可访问性修饰符

​ 类的内部成员的外部可访问性,由三个可访问性修饰符控制:public、private和protected。这三个修饰符的位置,都写在属性或方法的最前面。

# public

​ public修饰符表示这是公开成员,*外部可以自由访问*****。是默认修饰符,如果省略不写,实际上就带有该修饰符。因此,类的属性和方法默认都是外部可访问的。正常情况下,除非为了醒目和代码可读性,public都是省略不写的。

  • public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的

# private

​ private修饰符表示私有成员,*只能用在当前类的内部*****,*类的实例和子类都不能使用*****该成员。如果在类的内部,当前类的实例可以获取私有成员。

  • private 修饰的属性或方法是私有的,不能在声明它的类的外部访问

​ 严格地说,private定义的私有成员,并不是真正意义的私有成员。一方面,编译成JavaScript后,private关键字就被剥离了,这时外部访问该成员就不会报错。另一方面,由于前一个原因,TypeScript对于访问private成员没有严格禁止,使用方括号写法([])或者in运算符,实例对象就能访问该成员。 ​ 建议不使用private,改用 ES2022 的写法(属性名前加#),获得真正意义的私有成员。

​ 构造方法也可以是私有的,这就直接防止了使用new命令生成实例对象(实现了单例模式),只能在类的内部创建实例对象。当构造函数修饰为 private 时,该类不允许被继承或者实例化,当构造函数修饰为 protected 时,该类只允许被继承。

# protected

​ protected修饰符表示该成员是保护成员,*只能在类的内部使用****该成员,*实例无法使用***该成员,但是*子类内部可以使用****。子类不仅可以拿到父类的保护成员,还可以定义同名成员。在类的外部,实例对象不能读取保护成员,但是在类的内部可以。

  • protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的

# 实例属性的简写形式

​ 实际开发中,很多实例属性的值,是通过构造方法传入的。

class Point {
  x:number;
  y:number;
  constructor(x:number, y:number) { // 属性x和y的值是通过构造方法的参数传入的
    this.x = x;
    this.y = y;
  }
}
1
2
3
4
5
6
7
8

​ 这样的写法等于对同一个属性要声明两次类型,一次在类的头部,另一次在构造方法的参数里面。这有些累赘,TypeScript 就提供了一种简写形式。

class Point {
  constructor(
    public x:number, //这里的public不能省略
    public y:number
  ) {}
}
const p = new Point(10, 10);
p.x // 10
p.y // 10
1
2
3
4
5
6
7
8
9

构造方法的参数x前面有public修饰符,这时 TypeScript 就会自动声明一个公开属性x,不必在构造方法里面写任何代码,同时还会设置x的值为构造方法的参数值。

​ 除了public修饰符,构造方法的参数名只要有private、protected、readonly修饰符,都会自动声明对应修饰符的实例属性。readonly还可以与其他三个可访问性修饰符,一起使用。

# 静态成员

​ 类的内部可以使用static关键字,定义静态成员。静态成员是只能通过类本身使用的成员,不能通过实例对象使用。static关键字前面可以使用 public、private、protected 修饰符。静态私有属性也可以用ES6语法的 #前缀 表示。public和protected的静态成员可以被继承。

# 泛型类

​ 类也可以写成泛型,使用类型参数。注意,静态成员不能使用泛型的类型参数。

class Box<Type> { // 类Box有类型参数Type,因此属于泛型类。
  contents: Type;
  constructor(value:Type) {
    this.contents = value;
  }
}
// 新建实例时,变量的类型声明需要带有类型参数的值,不过本例等号左边的Box<string>可以省略不写
const b:Box<string> = new Box('hello!');  // 因为可以从等号右边推断得到
1
2
3
4
5
6
7
8

# 抽象类,抽象成员

​ TypeScript允许在类的定义前面,加上关键字abstract,表示该类不能被实例化,只能当作其他类的模板。这种类就叫做抽象类。抽象类只能当作基类使用,用来在它的基础上定义子类。

抽象类是不允许被实例化的。抽象类中的抽象方法必须被子类实现。即使是抽象方法,TypeScript 的编译结果中,仍然会存在这个类。

​ 抽象类的作用是,确保各种相关的子类都拥有跟基类相同的接口,可以看作是模板。其中的抽象成员都是必须由子类实现的成员,非抽象成员则表示基类已经实现的、由所有子类共享的成员。

​ 抽象类的子类也可以是抽象类,也就是说,抽象类可以继承其他抽象类。

​ 抽象类的内部可以有已经实现好的属性和方法,也可以有未实现的属性和方法。后者叫抽象成员,即属性名和方法名有abstract关键字,表示该方法需要子类实现。如果子类没有实现抽象成员,就会报错。

​ 这里有几个注意点。

  • 抽象成员只能存在于抽象类,不能存在于普通类。
  • 抽象成员不能有具体实现的代码。也就是说,已经实现好的成员前面不能加abstract关键字。
  • 抽象成员前也不能有private修饰符,否则无法在子类中实现该成员。
  • 一个子类最多只能继承一个抽象类。

# this 问题

​ 类的方法经常用到this关键字,它表示该方法当前所在的对象。

​ TypeScript 允许函数增加一个名为this的参数,放在参数列表的第一位,用来描述函数内部的this关键字的类型。编译时,TypeScript 一旦发现函数的第一个参数名为this,则会去除这个参数,即编译结果不会带有该参数。

​ this参数的类型可以声明为各种对象。

​ 在类的内部,this本身也可以当作类型使用,表示当前类的实例对象。

​ TypeScript提供了一个noImplicitThis编译选项。如果打开了这个设置项,如果this的值推断为any类型,就会报错。

​ 注意,this类型不允许应用于静态成员。静态成员拿不到实例对象。

​ 有些方法返回一个布尔值,表示当前的this是否属于某种类型。这时,这些方法的返回值类型可以写成this is Type的形式,其中用到了is运算符。

上次更新: 2024/4/13 07:03:33