一、 类型别名

类型别名用来给一个类型起个新名字。

  1. type Name = string;
  2. type NameResolver = () => string;
  3. type NameOrResolver = Name | NameResolver;
  4. function getName(n: NameOrResolver): Name {
  5. if (typeof n === 'string') {
  6. return n;
  7. } else {
  8. return n();
  9. }
  10. }

字符串字面量类型 用来约束取值只能是某几个字符串中的一个。


  1. type EventNames = 'click' | 'scroll' | 'mousemove';
  2. function handleEvent(ele: Element, event: EventNames) {
  3. // do something
  4. }
  5. handleEvent(document.getElementById('hello'), 'scroll'); // 没问题
  6. handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick'
  7. // Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.

二、 元组

数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象

  1. let xcatliu: [string, number] = ['Xcat Liu', 25];

越界的元素

当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型

  1. let xcatliu: [string, number];
  2. xcatliu = ['Xcat Liu', 25];
  3. xcatliu.push('http://xcatliu.com/');
  4. xcatliu.push(true);
  5. // Argument of type 'true' is not assignable to parameter of type 'string | number'.

三、枚举

枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。

  1. enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; // 使用关键字 enum

枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射:

  1. enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
  2. console.log(Days["Sun"] === 0); // true
  3. console.log(Days["Tue"] === 2); // true
  4. console.log(Days["Sat"] === 6); // true
  5. console.log(Days[0] === "Sun"); // true
  6. console.log(Days[2] === "Tue"); // true
  7. console.log(Days[6] === "Sat"); // true

手动赋值

  1. enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
  2. console.log(Days["Sun"] === 7); // true
  3. console.log(Days["Mon"] === 1); // true
  4. console.log(Days["Tue"] === 2); // true
  5. console.log(Days["Sat"] === 6); // true

由上可知 : 未手动赋值的枚举项会接着上一个枚举项递增

  1. enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};
  2. console.log(Days["Sun"] === 3); // true
  3. console.log(Days["Wed"] === 3); // true
  4. console.log(Days[3] === "Sun"); // false
  5. console.log(Days[3] === "Wed"); // true
  6. // 控制台打印的 Days
  7. {
  8. 1: "Mon"
  9. 2: "Tue"
  10. 3: "Wed"
  11. 4: "Thu"
  12. 5: "Fri"
  13. 6: "Sat"
  14. Fri: 5
  15. Mon: 1
  16. Sat: 6
  17. Sun: 3
  18. Thu: 4
  19. Tue: 2
  20. Wed: 3
  21. }

但: 递增到 3 的时候与前面的 Sun 的取值重复了,但是 TypeScript 并没有报错,导致 Days[3] 的值先是 "Sun",而后又被 "Wed" 覆盖了。因此,最好不要手动覆盖

**

常数项和计算所得项

枚举项有两种类型:常数项(constant member)和计算所得项(computed member)

  1. enum Color {Red, Green, Blue = "blue".length}; // 编译不会报错
  2. enum Color {Red = "red".length, Green, Blue}; // 编译会报错
  3. // Enum member must have initializer.

枚举(计算所得项) 如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错

**

常数枚举

常数枚举是使用 **const enum** 定义的枚举类型
> 常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员


  1. const enum Directions {
  2. Up,
  3. Down,
  4. Left,
  5. Right
  6. }
  7. let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];


外部枚举

外部枚举(Ambient Enums)是使用 declare enum 定义的枚举类型:

  1. declare enum Directions {
  2. Up,
  3. Down,
  4. Left,
  5. Right
  6. }
  7. let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

四、类

类的概念:

定义了一件事物的抽象特点,包含它的属性和方法

对象:

类的实例,通过new 生成

面向对象的特点:

封装: 将对数据操作的细节隐藏起来,只暴露对外的接口。外界调用端不需要知道细节,就能通过对外的接口来访问该对象,同时也能保证外界无法任意更改对象内部的数据

继承: 子类继承父类,子类除了拥有父类的所有特性,还有一些更具体的特性

多态: 由继承而产生了相关的不同的类,对同一个方法可以拥有不同的响应

存储器(getter & setter):

用于改变属性的读取和赋值行为

修饰符:

修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法

抽象类:

抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现

接口:

不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口

**

类的继承:

使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法

  1. class Animal {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. sayHi() {
  6. return `My name is ${this.name}`;
  7. }
  8. }
  9. class Cat extends Animal {
  10. constructor(name) {
  11. super(name); // 调用父类的 constructor(name)
  12. console.log(this.name);
  13. }
  14. sayHi() {
  15. return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
  16. }
  17. }
  18. let c = new Cat('Tom'); // Tom
  19. console.log(c.sayHi()); // Meow, My name is Tom

存储器:

使用 getter 和 setter 可以改变属性的赋值和读取行为

  1. class Animal {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. get name() {
  6. return 'Jack';
  7. }
  8. set name(value) {
  9. console.log('setter: ' + value);
  10. }
  11. }
  12. let a = new Animal('Kitty'); // setter: Kitty
  13. a.name = 'Tom'; // setter: Tom
  14. console.log(a.name); // Jack

类的静态方法:

使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用

  1. class Animal {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. get name() {
  6. return 'Jack';
  7. }
  8. set name(value) {
  9. console.log('setter: ' + value);
  10. }
  11. }
  12. let a = new Animal('Kitty'); // setter: Kitty
  13. a.name = 'Tom'; // setter: Tom
  14. console.log(a.name); // Jack

五、类与接口

接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述

类实现接口

一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现

  1. interface Alarm {
  2. alert();
  3. }
  4. class Door {
  5. }
  6. class SecurityDoor extends Door implements Alarm {
  7. alert() {
  8. console.log('SecurityDoor alert');
  9. }
  10. }
  11. class Car implements Alarm {
  12. alert() {
  13. console.log('Car alert');
  14. }
  15. }

一个类可以实现多个接口

  1. interface Alarm {
  2. alert();
  3. }
  4. interface Light {
  5. lightOn();
  6. lightOff();
  7. }
  8. class Car implements Alarm, Light {
  9. alert() {
  10. console.log('Car alert');
  11. }
  12. lightOn() {
  13. console.log('Car light on');
  14. }
  15. lightOff() {
  16. console.log('Car light off');
  17. }
  18. }

接口继承接口

使用 extends 关键字实现接口 继承接口

  1. interface Alarm {
  2. alert();
  3. }
  4. interface LightableAlarm extends Alarm {
  5. lightOn();
  6. lightOff();
  7. }

接口继承类

接口也可以继承类

  1. class Point {
  2. x: number;
  3. y: number;
  4. }
  5. interface Point3d extends Point {
  6. z: number;
  7. }
  8. let point3d: Point3d = {x: 1, y: 2, z: 3};

六、泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

  1. function createArray(length: number, value: any): Array<any> {
  2. let result = [];
  3. for (let i = 0; i < length; i++) {
  4. result[i] = value;
  5. }
  6. return result;
  7. }
  8. createArray<string>(3, 'x'); // ['x', 'x', 'x']
  • 上面代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型
  • Array 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 value的类型。

单个类型参数

  1. function createArray<T>(length: number, value: T): Array<T> {
  2. let result: T[] = [];
  3. for (let i = 0; i < length; i++) {
  4. result[i] = value;
  5. }
  6. return result;
  7. }
  8. createArray<string>(3, 'x'); // ['x', 'x', 'x']

上例中,我们在函数名后添加了 ,其中 T 用来指代任意输入的类型,在后面的输入 value: T 和输出 Array 中即可使用了。

多个类型参数

定义泛型的时候,可以一次定义多个类型参数:

  1. function swap<T, U>(tuple: [T, U]): [U, T] {
  2. return [tuple[1], tuple[0]];
  3. }
  4. swap([7, 'seven']); // ['seven', 7]

泛型的约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法。

  1. interface Lengthwise {
  2. length: number;
  3. }
  4. function loggingIdentity<T extends Lengthwise>(arg: T): T {
  5. console.log(arg.length);
  6. return arg;
  7. }
  • 上栗中使用接口对泛型进行约束,只允许这个函数传入那些包含 **length** 属性的变量。这就是泛型约束

  • 使用了 extends 约束了泛型 T 必须符合接口 Lengthwise 的形状,也就是必须包含 length 属性。

  • 如果调用 loggingIdentity 的时候,传入的 arg 不包含 length,那么在编译阶段就会报错了

  1. function copyFields<T extends U, U>(target: T, source: U): T {
  2. for (let id in source) {
  3. target[id] = (<T>source)[id];
  4. }
  5. return target;
  6. }
  7. let x = { a: 1, b: 2, c: 3, d: 4 };
  8. copyFields(x, { b: 10, d: 20 });
  • 上栗中使用了两个类型参数,其中要求 T 继承 U, 这样就保证了 U 上不会出现 T 中不存在的字段。

泛型接口

使用含有泛型的接口来定义函数的形状

  1. interface CreateArrayFunc {
  2. <T>(length: number, value: T): Array<T>;
  3. }
  4. let createArray: CreateArrayFunc;
  5. createArray = function<T>(length: number, value: T): Array<T> {
  6. let result: T[] = [];
  7. for (let i = 0; i < length; i++) {
  8. result[i] = value;
  9. }
  10. return result;
  11. }
  12. createArray(3, 'x'); // ['x', 'x', 'x']


泛型类

使用泛型定义类

  1. class GenericNumber<T> {
  2. zeroValue: T;
  3. add: (x: T, y: T) => T;
  4. }
  5. let myGenericNumber = new GenericNumber<number>();
  6. myGenericNumber.zeroValue = 0;
  7. myGenericNumber.add = function(x, y) { return x + y; };


泛型参数的默认类型

在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。

  1. function createArray<T = string>(length: number, value: T): Array<T> {
  2. let result: T[] = [];
  3. for (let i = 0; i < length; i++) {
  4. result[i] = value;
  5. }
  6. return result;
  7. }


七、声明的合并

如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型:


函数的合并

  1. function reverse(x: number): number;
  2. function reverse(x: string): string;
  3. function reverse(x: number | string): number | string {
  4. if (typeof x === 'number') {
  5. return Number(x.toString().split('').reverse().join(''));
  6. } else if (typeof x === 'string') {
  7. return x.split('').reverse().join('');
  8. }
  9. }


接口的合并

  1. interface Alarm {
  2. price: number;
  3. }
  4. interface Alarm {
  5. weight: number;
  6. }

相当于:

  1. interface Alarm {
  2. price: number;
  3. weight: number;
  4. }

注意,合并的属性的类型必须是唯一的

  1. interface Alarm {
  2. price: number;
  3. }
  4. interface Alarm {
  5. price: string; // 类型不一致,会报错
  6. weight: number;
  7. }
  8. // index.ts(5,3): error TS2403: Subsequent variable declarations must have the same type. Variable 'price' must be of type 'number', but here has type 'string'.

接口中方法的合并,与函数的合并一样:

  1. interface Alarm {
  2. price: number;
  3. alert(s: string): string;
  4. }
  5. interface Alarm {
  6. weight: number;
  7. alert(s: string, n: number): string;
  8. }

相当于:

  1. interface Alarm {
  2. price: number;
  3. weight: number;
  4. alert(s: string): string;
  5. alert(s: string, n: number): string;
  6. }

类的合并

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