TypeScript的核心原则之一是对值所具有的结构举行类型检查。 它偶然被称做“鸭式辨型法”或“结构性子类型化”。即如果一个东西走起来像鸭子、叫起来像鸭子,那它就是鸭子。
通过定义接口,为特定的结构赋予了一个明确的名称和规范。
在 TypeScript 中,接口(Interface)用于定义对象的外形,即对象应该具有哪些属性以及这些属性的类型。
使用 interface 关键字来定义接口
- interface Person {
- name: string;
- age: number;
- }
复制代码 定义了一个名为 Person 的接口:
- name:必须的属性,且类型为字符串 string 。
- age: 必须的属性,类型为数字 number 。
实现接口
一个对象或类可以明确声明它实现了某个接口,以满意接口定义的属性要求。
- // person1 合法
- let person1: Person = {
- name: "张三",
- age: 30
- };
- // person2 不合法: 对象字面量只能指定已知属性,并且“likes”不在类型“Person”中。
- let person2: Person = {
- name: "李四",
- age: 25,
- likes: "dog"
- };
复制代码 TypeScript 的类型体系非常严酷,将一个对象指定为某个特定接口的类型时,它必须仅包罗该接口中定义的属性,不能多也不能少。
所以,由于 likes 属性不在 Person 接口的定义中,TypeScript 会以为对象 person2 的结构不符合 Person 接口的要求,从而导致编译错误。
可选属性
在属性名后添加 ? 来标志属性为可选的。
- interface Person {
- address?: string;
- }
- let person1: Person = {}; // 合法, 可以没有 address 属性
- let person2: Person = {
- address: "xxx街道xxx小区"
- };
复制代码 定义了一个名为 Person 的接口:
- address:可选属性,其类型为字符串 string 。创建符合 Person 接口的对象时, address 属性可有可无。
只读属性
在属性名前用 readonly来指定只读属性。
- interface Person {
- readonly id: number;
- }
- // 类型 "{}" 中缺少属性 "id",但类型 "Person" 中需要该属性。
- let person1: Person = {}; // Error: Property 'id' is missing in type '{}' but required in type 'Person'.
- // 赋值后,id 的值就不能再改变了。
- let person2: Person = {
- id: 1,
- };
- person2.id = 2; // Error: 无法为“id”赋值,因为它是只读属性。
复制代码 定义了一个名为 Person 的接口:
- id : 必须的属性,该属性只读。对象创建后,id 的值不能被重新赋值修改。
ReadonlyArray 类型用于表现一个只读的数组
创建 ReadonlyArray 后,就不能对其元素举行修改操作,比方添加、删除或修改元素的值:
- let readonlyArr: ReadonlyArray<number> = [1, 2, 3];
- // 以下操作会报错,因为不能修改只读数组的元素
- // 类型“readonly number[]”中的索引签名仅允许读取。
- readonlyArr[0] = 4;
- // 类型“readonly number[]”上不存在属性“push”。
- readonlyArr.push(4);
- // 类型“readonly number[]”上不存在属性“pop”。
- readonlyArr.pop();
- // 无法为“length”赋值,因为它是只读属性。
- readonlyArr.length = 10;
复制代码 ReadonlyArray<T> 与 Array<T>的区别:
- Array<T> 是普通的可修改数组类型,允许举行添加、删除、修改元素等操作。
普通数组可以使用 push 、 pop 、 splice 等方法来修改数组的内容。
- ReadonlyArray<T> 去掉了这些可变的方法,以确保数组在被使用时不会被意外修改。
不能将只读数组直接赋值给普通数组,类型不兼容:
- let arr: Array<number> = [1, 2, 3];
- let readonlyArr: ReadonlyArray<number> = [1, 2, 3];
- // Error: The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
- // 类型 "readonly number[]" 为 "readonly",不能分配给可变类型 "number[]"。
- arr = readonlyArr
复制代码 可以使用类型断言逼迫的类型转换:
- arr = readonlyArr as number[];
复制代码 ReadonlyArray 会把常用于需要确保数组不被意外修改的场景,以增强代码的安全性和可预测性。
readonly vs const
- readonly(用于接口中的属性):
- 应用于对象的属性,表现该属性在对象创建后不能被重新赋值。
- interface Person {
- readonly name: string;
- }
- let person: Person = { name: "张三" };
- // 无法为“name ”重新赋值,因为它是只读属性。
- person.name = "李四";
- let mutablePerson = person;
- // 无法为“name ”重新赋值,因为它是只读属性。
- mutablePerson.name = "李四";
复制代码 把 person 赋值给了 mutablePerson,mutablePerson 现实上和 person 指向的是同一个对象。
由于 Person 接口中定义了 name 为只读属性,所以无论是通过 person 还是 mutablePerson 来尝试修改 name 的值,都是不被允许的,都会触发类型检查错误,提示不能对只读属性举行重新赋值。
这体现了 TypeScript 对接口中只读属性的严酷类型检查,确保了在任何对该对象的操作中,都遵循了只读属性的约束。
- const
- const声明一个只读的常量。一旦声明,常量的值就不能改变
- 对于基本数据类型(如数字、字符串、布尔值等),const 声明的变量的值是不可变的。
- 对于引用数据类型(如对象、数组等),const 只是保证变量的引用地点不能改变,但可以修改引用对象的内部属性。
- const num = 5;
- // 报错,不能重新给 const 基本类型变量赋值
- num = 6; // Cannot assign to 'num' because it is a constant.
- const person = { name: "张三" };
- person.name = "李四"; // 可以修改name 属性,因为person对象的引用地址没有改变
- person = { name: "李四" }; // 报错,不能重新给 const 引用类型变量 重新 赋值 引用地址
复制代码 额外属性检查
定义了一个函数 printPersonInfo,它接受一个参数 infoObj,其类型为 Person :
- interface Person {
- name: string;
- age?: number;
- }
- function printPersonInfo(infoObj: Person) {
- console.log(infoObj);
- }
- // Object literal may only specify known properties, and 'likes' does not exist in type 'Person'.
- // 对象字面量只能指定已知属性,并且“likes”不在类型“Person”中。
- printPersonInfo({ name: "李四", age: 25, likes: "dog" }); // 报错
复制代码 调用printPersonInfo()时,传入方法的参数 具有name属性,满意 Person 接口的部分要求。age是可选参数,可要可不要。有一个额外的likes属性。
为什么会报错??
参数是以 对象字面量 的情势直接传递给printPersonInfo()方法。
当把对象字面量赋值给变量大概作为参数传递的时候,对象字面量会被特殊对待而且会经过 额外属性检查。 如果一个对象字面量存在任何“目标类型”不包罗的属性时,会编译错误。
怎样绕开检查
- printPersonInfo({ name: "李四", age: 25, likes: "dog" } as Person);
复制代码
- 添加索引签名[propName: string]:any :
- interface Person {
- name: string;
- age?: number;
- [propName: string]:any;
- }
复制代码 [propName: string]: any 是一个索引签名。它表现可以有任意数量的额外属性,属性名是字符串类型,属性值可以是任何类型。
这样的接口定义提供了一定的灵活性。既规定了一些明确的必须和可选属性(name 和 age),又允许对象具有其他任意的属性。
- 将这个对象赋值给一个另一个变量: 由于 myObj 不会经过额外属性检查,所以编译器不会报错。
- let myObj = { name: "李四", age: 25, likes: "dog" }; // 参数合法
- printPersonInfo(myObj);
复制代码 对象 myObj 有额外的 likes 属性,为什么不报错?
由于对象 myObj本身是一个普通对象, 它没有类型,不会经过额外属性检查。在作为参数传递时,myObj具有 Person 接口要求的 name 、 age属性,所以可以将 myObj作为参数传递给 printPersonInfo函数。
在 TypeScript 中,当将一个对象作为参数传递给一个函数,而且函数盼望的参数类型是一个接口时,如果该对象包罗了接口中定义的须要属性,纵然它还具有额外的属性,也会被以为是合法的传递。
这体现了 TypeScript 基于结构的类型检查原则,只要对象满意接口定义的须要属性,就可以被视为该接口类型的对象。
函数类型
接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
为了使用接口表现函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
定义一个名为 MathOperation 的接口,它表现一个接受两个数字类型的参数 x 和 y ,并返回一个数字的函数类型:
- interface MathOperation {
- (x: number, y: number): number;
- }
复制代码 我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了怎样创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。
- let sum: MathOperation;
- sum = function(x: number, y: number): number{
- return x + y
- }
- // 简洁写法:let sum: MathOperation = (x, y) => x + y;
- console.log( sum(10, 15) ); // 25
复制代码 声明变量 sum ,其类型为 MathOperation 。
接下来,将一个匿名函数赋值给 sum 。这个匿名函数接受两个数字参数 x 和 y,并返回它们的和,符合 MathOperation 接口定义的函数结构。
对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。
参数 x 和 y可以用其他变量代替。
- let sum: MathOperation;
- sum = function(a: number, b: number): number{
- return a + b
- }
复制代码 函数的参数会逐个举行检查,要求对应位置上的参数类型是兼容的。 如果不想指定类型,TypeScript的类型体系会推断出参数类型,由于函数直接赋值给了 MathOperation 类型变量。 函数的返回值类型是通过其返回值推断出来的。
- let sum: MathOperation;
- sum = function(x, y){
- return x + y
- }
复制代码 可索引的类型
可索引类型具有一个 索引签名,它描述了对象索引的类型,另有相应的索引返回值类型。
示例:
- interface StringArray {
- [index: number]: string;
- }
- let myArray: StringArray;
- myArray = ["Bob", "Fred"];
- let myStr: string = myArray[0]; // Bob
复制代码 定义StringArray 接口,它具有索引签名。 这个索引签名表现了当用 number去索引StringArray时会得到string类型的返回值。
TypeScript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是由于当使用 number 来索引时,JavaScript会将它转换成 string 然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。
- class Animal {
- name: string;
- }
- class Dog extends Animal {
- breed: string;
- }
- // 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!
- interface NotOkay {
- [x: number]: Animal;
- [x: string]: Dog;
- }
复制代码 在这段代码中,定义的 NotOkay 接口会导致错误。
由于在一个接口中同时定义了数字索引类型为 Animal 和字符串索引类型为 Dog 。
当使用索引来访问对象时:
- 使用数字索引,盼望得到的是 Animal 类型;
- 使用字符串索引,盼望得到的是 Dog 类型。
但在现实环境中,这样的混淆定义可能会导致混乱和不一致,由于无法明确在特定索引下应该返回哪种确切的类型。
这违反了 TypeScript 对于类型定义的一致性和明确性原则。
字符串索引签名能够很好的描述dictionary模式,而且它们也会确保全部属性与其返回值类型相匹配。 由于字符串索引声明了 obj.property 和 obj["property"] 两种情势都可以。
示例中, name的类型与字符串索引类型不匹配,所以类型检查器给出一个错误提示:
- interface NumberDictionary {
- [index: string]: number;
- length: number; // 可以,length是number类型
- name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
- }
复制代码 [index: string]: number; 这表现通过字符串索引访问这个对象时,返回的值应该是数字类型。
由于按照接口的定义,全部属性的值都应该是数字类型,但 name 的类型被定义为 string,与索引返回值的类型不匹配。
将索引签名设置为只读,防止给索引赋值:
- interface ReadonlyStringArray {
- readonly [index: number]: string;
- }
- let myArray: ReadonlyStringArray = ["Alice", "Bob"];
- myArray[2] = "Mallory"; // 类型“ReadonlyStringArray”中的索引签名仅允许读取。
复制代码 类类型
在 TypeScript 中,“类类型”指的是使用 class 关键字定义的一种结构,用于描述具有特定属性、方法和行为的对象的模板。
类类型可以包罗以下主要元素:
- 属性:描述对象的数据成员,具有特定的类型。
- 构造函数:用于初始化对象的属性,在创建对象实例时被调用。
- 方法:定义对象可以实行的操作。
implements 关键字
在 TypeScript 中,implements 关键字用于让一个类实现一个或多个接口。
当一个类使用 implements 关键字后跟一个接口名称时,它必须满意该接口所定义的全部属性和方法的要求。
这意味着:
- 类必须具有接口中定义的全部属性,而且属性的类型必须完全匹配。
- 类必须实现接口中定义的全部方法,方法的名称、参数列表和返回值类型都要与接口中的定义一致。
示例:
- interface MyInterface {
- method1(): void;
- property1: string;
- }
- class MyClass implements MyInterface {
- property1: string = "value";
- method1(): void {
- // 方法的实现
- }
- }
复制代码 在上述示例中,MyClass 类实现了 MyInterface 接口。它具有与接口中定义相同的属性 property1 和方法 method1 ,而且属性的类型和方法的签名都符合接口的要求。
类静态部分与实例部分的区别
类是具有两个类型的:静态部分的类型和实例的类型。
定义一个名为 ClockConstructor 的接口:
- interface ClockConstructor {
- new (hour: number, minute: number);
- }
复制代码 ClockConstructor 接口定义了一个构造函数的签名,要求实现这个接口的类必须具有一个接受两个参数(一个数字类型的 hour 和一个数字类型的 minute )的构造函数。
然后定义了 Clock 类实现 ClockConstructor 接口:
- class Clock implements ClockConstructor {
- constructor(h: number, m: number) { }
- }
复制代码 理论上:Clock 类的构造函数 constructor(h: number, m: number) 符合 ClockConstructor 接口中定义的构造函数要求,所以实现了该接口。
现实上:报错了!!
由于当一个类实现了一个接口时,只对其实例部分举行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。
因此,需要直接操作类的静态部分。定义两个接口: ClockConstructor为构造函数所用和ClockInterface为实例方法所用
- interface ClockConstructor {
- new (hour: number, minute: number): ClockInterface;
- }
- interface ClockInterface {
- tick(): any;
- }
- class Clock implements ClockInterface {
- constructor(h: number, m: number) { }
- tick() {
- console.log("beep beep");
- }
- }
- // 实例化
- let myClock = new Clock(5, 30);
- myClock.tick(); // beep beep
复制代码
- ClockConstructor 接口定义了一个构造函数的签名。ClockConstructor 接口规定了任何实现这个接口的构造函数,都必须接受两个数字类型的参数,并创建出符合 ClockInterface 接口的对象。
- new 关键字表明这是在描述一个构造函数。
- (hour: number, minute: number) 是构造函数的参数列表。这表现该构造函数应接受两个number类型的参数 hour 和 minute 。
- : ClockInterface 是构造函数的返回值类型。当使用这个构造函数创建对象时,所创建的对象应该符合 ClockInterface 接口的定义。这就对通过这个构造函数创建的对象施加了一种约束,要求它们具有 ClockInterface 中规定的属性和方法。
- 这样定义的目的是为了统一规定创建时钟类的构造函数的参数和创建出的对象的类型。
- ClockInterface 接口定义了一个方法 tick() ,表现实现该接口的类需要具有这个方法。
- 定义 Clock 类 实现 ClockInterface 接口:
- Clock 类必须提供tick方法。
- Clock 类的构造函数 constructor(h: number, m: number) 符合 ClockConstructor 接口中定义的构造函数要求。
注意:接口中定义的构造函数的返回值类型应该是一个详细的类型。
如果希望表现构造函数创建的对象不返回任何以意义的值,可以将返回值类型指定为 void ;如果希望指定创建的对象的类型,应该是一个详细的类或接口类型。
- // 表示构造函数创建的对象不返回任何有意义的值,将返回值类型指定为 `void`
- interface ClockConstructor {
- new (hour: number, minute: number): void;
- }
- // 返回值是一个具体的类型
- interface ClockConstructor {
- new (hour: number, minute: number): ClockInterface; // 假设 ClockInterface 是已定义的接口
- }
复制代码 接口继承接口
一个接口可以继承自另一个接口,从而扩展其属性和方法;可以更灵活地将接口分割到可重用的模块里。
- interface Person {
- name: string;
- age: number;
- }
- interface Employee extends Person { salary: number;}let Employee1: Employee = { name: "张三", age: 30, salary: 3000};
复制代码 一个接口可以继承多个接口,创建出多个接口的合成接口。
- // Shape 接口定义了一个计算面积的方法 area
- interface Shape {
- area(): number;
- }
- // Colorable 接口定义了一个表示颜色的属性 color
- interface Colorable {
- color: string;
- }
- // Square 接口继承了 Shape 和 Colorable 接口,并且添加了一个表示边长的属性 sideLength 。
- interface Square extends Shape, Colorable {
- sideLength: number;
- }
- // MySquare 类 实现了 Square 接口
- class MySquare implements Square {
- sideLength: number;
- color: string;
- constructor(sideLength: number, color: string) {
- this.sideLength = sideLength;
- this.color = color;
- }
- area(): number {
- return this.sideLength * this.sideLength;
- }
- }
- let mySquare = new MySquare(5, "red");
- console.log(mySquare.area()); // 25
- console.log(mySquare.color); // red
复制代码 Square 接口是包罗了 Shape 和 Colorable 接口的特性的合成接口。
实现 Square 接口的类需要同时满意这三个接口的要求。
接口继承类
在 TypeScript 中,接口可以继承类。
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了全部类中存在的成员,但并没有提供详细实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受掩护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
示例:
- class Point {
- x: number;
- y: number;
- constructor(x: number, y: number) {
- this.x = x;
- this.y = y;
- }
- distanceFromOrigin(): number {
- return Math.sqrt(this.x * this.x + this.y * this.y);
- }
- }
- interface Point3D extends Point {
- z: number;
- }
- class MyPoint3D implements Point3D {
- x: number;
- y: number;
- z: number;
- constructor(x: number, y: number, z: number) {
- this.x = x;
- this.y = y;
- this.z = z;
- }
- distanceFromOrigin(): number {
- // 重写父类的方法
- console.log(this.x, this.y, this.z);
- const distanceSquared = this.x * this.x + this.y * this.y + this.z * this.z;
- return Math.sqrt(distanceSquared);
- }
- }
- let myP = new MyPoint3D(10, 20, 30);
- console.log( myP.distanceFromOrigin() );
复制代码
- Point 类具有 x、y 属性和 distanceFromOrigin 方法。
- Point3D 接口继承了 Point 类。
- 继承属性:接口 Point3D 继承了类 Point 的属性 x 和 y 的声明。
- 不继承实现:接口 Point3D 继承了类 Point的属性和方法的声明,但它并没有继承类中方法的详细实现。比如,distanceFromOrigin 方法的实现不会被继承,实现 Point3D 接口的类需要本身提供这个方法的实现(大概选择不实现,如果不是必须的)。
- 新增属性:在继承的底子上,接口 Point3D 增加了新的属性 z。
- MyPoint3D 类实现了 Point3D 接口。在MyPoint3D类中:
- 必须明确地实现接口中继承的属性 x、y 以及新增的属性 z 。
- 由于接口 Point3D没有继承方法的实现,所以 MyPoint3D 类需要提供 distanceFromOrigin 方法的实现。
混淆类型
在 TypeScript 中,混淆类型(Hybrid Type)是指一个对象既具有属性又具有方法,同时还可能表现出一些函数的特性。
示例:
- interface Counter {
- count: number;
- (): number;
- increment(): void;
- }
- // 声明变量counter,并指定它的类型是 Counter接口
- let counter: Counter = {
- count: 0,
- increment() {
- this.count++;
- },
- // 作为函数被调用时返回当前计数
- () {
- return this.count;
- }
- };
- counter.increment();
- console.log(counter());
复制代码 声明变量 counter ,并指定其类型为 Counter 接口类型。这意味着:
counter 变量必须具有符合 Counter 接口定义的结构和行为。它需要有一个数值类型的属性 count ,一个无返回值的方法 increment ,以及一个能返回数值的函数调用情势 ()
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |