三、TypeScript 常用类型
8、接口
当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的。
1. 使用 interface 关键字来声明接口。
2. 接口名称(比如,此处的 IPerson),可以是任意合法的变量名称。
3. 声明接口后,直接使用接口名称作为变量的类型。
4. 因为每一行只有一个属性类型,因此,属性类型后没有 ;(分号)。
interface(接口)和 type(类型别名)的对比: 相同点:都可以给对象指定类型。 不同点: 接口,只能为对象指定类型。 l 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名。
如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用。
1. 使用 extends(继承)关键字实现了接口 Point3D 继承 Point2D。
2. 继承后,Point3D 就有了 Point2D 的所有属性和方法(此时,Point3D 同时有 x、y、z 三个属性)
9、元组
场景:在地图中,使用经纬度坐标来标记位置信息。 可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型。
使用 number[] 的缺点:不严谨,因为该类型的数组中可以出现任意多个数字。 更好的方式:元组(Tuple)。 元组类型是另一种类型的数组,它确切地知道包含多少个元素,以及特定索引对应的类型。
let position:[number,number]=[39.5427,116.2317]
1. 元组类型可以确切地标记出有多少个元素,以及每个元素的类型。
2. 该示例中,元素有两个元素,每个元素的类型都是 number。
10、类型推论
在 TS 中,某些没有明确指出类型的地方,TS 的类型推论机制会帮助提供类型。 换句话说:由于类型推论的存在,这些地方,类型注解可以省略不写! 发生类型推论的 2 种常见场景:1、 声明变量并初始化时 2、 决定函数返回值时
能省略类型注解的地方就省略(偷懒,充分利用TS类型推论的能力,提升开发效率)。
11、类型断言
有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。
getElementById 方法返回值的类型是 HTMLElement,该类型只包含所有标签公共的属性或方法,不包含 a 标签特有的 href 等属性。 因此,这个类型太宽泛(不具体),无法操作 href 等 a 标签特有的属性或方法。 解决方式:这种情况下就需要使用类型断言指定更加具体的类型。
1. 使用 as 关键字实现类型断言。
2. 关键字 as 后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement 的子类型)。
3. 通过类型断言,aLink 的类型变得更加具体,这样就可以访问 a 标签特有的属性或方法了。 另一种语法,使用 <> 语法,这种语法形式不常用知道即可: 技巧:在浏览器控制台,通过 console.dir() 打印 DOM 元素,在属性列表的最后面,即可看到该元素的类型。
12、字面量类型
通过 TS 类型推论机制,可以得到答案:
1. 变量 str1 的类型为:string。
2. 变量 str2 的类型为:'Hello TS'。
解释:
1. str1 是一个变量(let),它的值可以是任意字符串,所以类型为:string。
2. str2 是一个常量(const),它的值不能变化只能是 'Hello TS',所以,它的类型为:'Hello TS'。 注意:此处的 'Hello TS',就是一个字面量类型。也就是说某个特定的字符串也可以作为 TS 中的类型。 除字符串外,任意的 JS 字面量(比如,对象、数字等)都可以作为类型使用
使用模式:字面量类型配合联合类型一起使用。 使用场景:用来表示一组明确的可选值列表
比如,在贪吃蛇游戏中,游戏的方向的可选值只能是上、下、左、右中的任意一个。
参数 direction 的值只能是 up/down/left/right 中的任意一个。 优势:相比于 string 类型,使用字面量类型更加精确、严谨。
13、枚举
枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值。 枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个。
1. 使用 enum 关键字定义枚举。
2. 约定枚举名称、枚举中的值以大写字母开头。
3. 枚举中的多个值之间通过 ,(逗号)分隔。
4. 定义好枚举后,直接使用枚举名称作为类型注解。
注意:形参 direction 的类型为枚举 Direction,那么,实参的值就应该是枚举 Direction 成员的任意一个。 访问枚举成员:
类似于 JS 中的对象,直接通过点(.)语法访问枚举的成员。
枚举成员是有值的,默认为:从 0 开始自增的数值。 我们把,枚举成员的值为数字的枚举,称为:数字枚举。 当然,也可以给枚举中的成员初始化值。
字符串枚举:枚举成员的值是字符串。字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值。
枚举是 TS 为数不多的非 JavaScript 类型级扩展(不仅仅是类型)的特性之一。 因为:其他类型仅仅被当做类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)。 也就是说,其他的类型会在编译为 JS 代码时自动移除。但是,枚举类型会被编译为 JS 代码!
枚举与前面讲到的字面量类型+联合类型组合的功能类似,都用来表示一组明确的可选值列表。 一般情况下,推荐使用字面量类型+联合类型组合的方式,因为相比枚举,这种方式更加直观、简洁、高效。
14、any类型
原则:不推荐使用 any!这会让 TypeScript 变为 “AnyScript”(失去 TS 类型保护的优势)。 因为当值的类型为 any 时,可以对该值进行任意操作,并且不会有代码提示。
以上操作都不会有任何类型错误提示,即使可能存在错误! 尽可能的避免使用 any 类型,除非临时使用 any 来“避免”书写很长、很复杂的类型! 其他隐式具有 any 类型的情况:1 声明变量不提供类型也不提供默认值 2 函数参数不加类型。 注意:因为不推荐使用 any,所以,这两种情况下都应该提供类型!
15、typeof
众所周知,JS 中提供了 typeof 操作符,用来在 JS 中获取数据的类型。
实际上,TS 也提供了 typeof 操作符:可以在类型上下文中引用变量或属性的类型(类型查询)。 使用场景:根据已有变量的值,获取该值的类型,来简化类型书写。
1. 使用 typeof 操作符来获取变量 p 的类型,结果与第一种(对象字面量形式的类型)相同。
2. typeof 出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文(区别于 JS 代码)。
3. 注意:typeof 只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)。
四、TypeScript高级类型
1、class类
TypeScript 全面支持 ES2015 中引入的 class 关键字,并为其添加了类型注解和其他语法(比如,可见性修饰符等)。
class Person{}
const p=new person()
1. 根据 TS 中的类型推论,可以知道 Person 类的实例对象 p 的类型是 Person。
2. TS 中的 class,不仅提供了 class 的语法功能,也作为一种类型存在。
实例初始化
1. 声明成员 age,类型为 number(没有初始值)。
2. 声明成员 gender,并设置初始值,此时,可省略类型注解(TS 类型推论 为 string 类型)。
构造函数:
1. 成员初始化(比如,age: number)后,才可以通过 this.age 来访问实例成员。
2. 需要为构造函数指定类型注解,否则会被隐式推断为 any;构造函数不需要返回值类型。
实例方法:
方法的类型注解(参数和返回值)与函数用法相同。
继承:
类继承的两种方式:1 extends(继承父类) 2 implements(实现接口)。 说明:JS 中只有 extends,而 implements 是 TS 提供的。
1. 通过 extends 关键字实现继承。
2. 子类 Dog 继承父类 Animal,则 Dog 的实例对象 dog 就同时具有了父类 Animal 和 子类 Dog 的所有属性和方法。
1. 通过 implements 关键字让 class 实现接口。
2. Person 类实现接口 Singable 意味着,Person 类中必须提供 Singable 接口中指定的所有方法和属性。
类成员可见性:可以使用 TS 来控制 class 的方法或属性对于 class 外的代码是否可见。
可见性修饰符包括:1 public(公有的) 2 protected(受保护的) 3 private(私有的)。
public:表示公有的、公开的,公有成员可以被任何地方访问,默认可见性。
1. 在类属性或方法前面添加 public 关键字,来修饰该属性或方法是共有的。
2. 因为 public 是默认可见性,所以,可以直接省略。
protected:表示受保护的,仅对其声明所在类和子类中(非实例对象)可见。
1. 在类属性或方法前面添加 protected 关键字,来修饰该属性或方法是受保护的。
2. 在子类的方法内部可以通过 this 来访问父类中受保护的成员,但是,对实例不可见!
private:表示私有的,只在当前类中可见,对实例对象以及子类也是不可见的。
1. 在类属性或方法前面添加 private 关键字,来修饰该属性或方法是私有的。
2. 私有的属性或方法只在当前类中可见,对子类和实例对象也都是不可见的!
readonly:表示只读,用来防止在构造函数之外对属性进行赋值。
1. 使用 readonly 关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法。
2. 注意:属性 age 后面的类型注解(比如,此处的 number)如果不加,则 age 的类型为 18 (字面量类型)。
3. 接口或者 {} 表示的对象类型,也可以使用 readonly。
2、类型兼容性
两种类型系统:1 Structural Type System(结构化类型系统) 2 Nominal Type System(标明类型系统)。 TS 采用的是结构化类型系统,也叫做 duck typing(鸭子类型),类型检查关注的是值所具有的形状。 也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型
1. Point 和 Point2D 是两个名称不同的类。
2. 变量 p 的类型被显示标注为 Point 类型,但是,它的值却是 Point2D 的实例,并且没有类型错误。
3. 因为 TS 是结构化类型系统,只检查 Point 和 Point2D 的结构是否相同(相同,都具有 x 和 y 两个属性,属性类型也相同)。
4. 但是,如果在 Nominal Type System 中(比如,C#、Java 等),它们是不同的类,类型无法兼容。
注意:在结构化类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型,这种说法并不准确。 更准确的说法:对于对象类型来说,y 的成员至少与 x 相同,则 x 兼容 y(成员多的可以赋值给少的)。
1. Point3D 的成员至少与 Point 相同,则 Point 兼容 Point3D。
2. 所以,成员多的 Point3D 可以赋值给成员少的 Point。
除了 class 之外,TS 中的其他类型也存在相互兼容的情况,包括:1 接口兼容性 2 函数兼容性 等。接口之间的兼容性,类似于 class。并且,class 和 interface 之间也可以兼容。
函数之间兼容性比较复杂,需要考虑:1 参数个数 2 参数类型 3 返回值类型。
1. 参数个数,参数多的兼容参数少的(或者说,参数少的可以赋值给多的)。
1. 参数少的可以赋值给参数多的,所以,f1 可以赋值给 f2。
2. 数组 forEach 方法的第一个参数是回调函数,该示例中类型为:(value: string, index: number, array: string[]) => void。
3. 在 JS 中省略用不到的函数参数实际上是很常见的,这样的使用方式,促成了 TS 中函数类型之间的兼容性。
4. 并且因为回调函数是有类型的,所以,TS 会自动推导出参数 item、index、array 的类型。
2. 参数类型,相同位置的参数类型要相同(原始类型)或兼容(对象类型)。
函数类型 F2 兼容函数类型 F1,因为 F1 和 F2 的第一个参数类型相同。
1. 注意,此处与前面讲到的接口兼容性冲突。
2. 技巧:将对象拆开,把每个属性看做一个个参数,则,参数少的(f2)可以赋值给参数多的(f3)。
3. 返回值类型,只关注返回值类型本身即可:
1. 如果返回值类型是原始类型,此时两个类型要相同,比如,左侧类型 F5 和 F6。
2. 如果返回值类型是对象类型,此时成员多的可以赋值给成员少的,比如,右侧类型 F7 和 F8。
3、交叉类型
交叉类型(&)和接口继承(extends)的对比:
相同点:都可以实现对象类型的组合。
不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同。
以上代码,接口继承会报错(类型不兼容);交叉类型没有错误,可以简单的理解为: