一、 类型别名
类型别名用来给一个类型起个新名字。
type Name = string;type NameResolver = () => string;type NameOrResolver = Name | NameResolver;function getName(n: NameOrResolver): Name {if (typeof n === 'string') {return n;} else {return n();}}
字符串字面量类型 用来约束取值只能是某几个字符串中的一个。
type EventNames = 'click' | 'scroll' | 'mousemove';function handleEvent(ele: Element, event: EventNames) {// do something}handleEvent(document.getElementById('hello'), 'scroll'); // 没问题handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick'// Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.
二、 元组
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象
let xcatliu: [string, number] = ['Xcat Liu', 25];
越界的元素
当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型
let xcatliu: [string, number];xcatliu = ['Xcat Liu', 25];xcatliu.push('http://xcatliu.com/');xcatliu.push(true);// Argument of type 'true' is not assignable to parameter of type 'string | number'.
三、枚举
枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; // 使用关键字 enum
枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射:
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};console.log(Days["Sun"] === 0); // trueconsole.log(Days["Tue"] === 2); // trueconsole.log(Days["Sat"] === 6); // trueconsole.log(Days[0] === "Sun"); // trueconsole.log(Days[2] === "Tue"); // trueconsole.log(Days[6] === "Sat"); // true
手动赋值
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};console.log(Days["Sun"] === 7); // trueconsole.log(Days["Mon"] === 1); // trueconsole.log(Days["Tue"] === 2); // trueconsole.log(Days["Sat"] === 6); // true
由上可知 : 未手动赋值的枚举项会接着上一个枚举项递增
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};console.log(Days["Sun"] === 3); // trueconsole.log(Days["Wed"] === 3); // trueconsole.log(Days[3] === "Sun"); // falseconsole.log(Days[3] === "Wed"); // true// 控制台打印的 Days{1: "Mon"2: "Tue"3: "Wed"4: "Thu"5: "Fri"6: "Sat"Fri: 5Mon: 1Sat: 6Sun: 3Thu: 4Tue: 2Wed: 3}
但: 递增到
3的时候与前面的Sun的取值重复了,但是 TypeScript 并没有报错,导致Days[3]的值先是"Sun",而后又被"Wed"覆盖了。因此,最好不要手动覆盖
常数项和计算所得项
枚举项有两种类型:常数项(constant member)和计算所得项(computed member)
enum Color {Red, Green, Blue = "blue".length}; // 编译不会报错enum Color {Red = "red".length, Green, Blue}; // 编译会报错// Enum member must have initializer.
枚举(计算所得项) 如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错
常数枚举
常数枚举是使用 **
const enum** 定义的枚举类型
> 常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。
const enum Directions {Up,Down,Left,Right}let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
外部枚举
外部枚举(Ambient Enums)是使用
declare enum定义的枚举类型:
declare enum Directions {Up,Down,Left,Right}let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
四、类
类的概念:
定义了一件事物的抽象特点,包含它的属性和方法
对象:
类的实例,通过new 生成
面向对象的特点:
封装: 将对数据操作的细节隐藏起来,只暴露对外的接口。外界调用端不需要知道细节,就能通过对外的接口来访问该对象,同时也能保证外界无法任意更改对象内部的数据
继承: 子类继承父类,子类除了拥有父类的所有特性,还有一些更具体的特性
多态: 由继承而产生了相关的不同的类,对同一个方法可以拥有不同的响应
存储器(getter & setter):
用于改变属性的读取和赋值行为
修饰符:
修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
抽象类:
抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
接口:
不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
类的继承:
使用
extends关键字实现继承,子类中使用super关键字来调用父类的构造函数和方法
class Animal {constructor(name) {this.name = name;}sayHi() {return `My name is ${this.name}`;}}class Cat extends Animal {constructor(name) {super(name); // 调用父类的 constructor(name)console.log(this.name);}sayHi() {return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()}}let c = new Cat('Tom'); // Tomconsole.log(c.sayHi()); // Meow, My name is Tom
存储器:
使用 getter 和 setter 可以改变属性的赋值和读取行为
class Animal {constructor(name) {this.name = name;}get name() {return 'Jack';}set name(value) {console.log('setter: ' + value);}}let a = new Animal('Kitty'); // setter: Kittya.name = 'Tom'; // setter: Tomconsole.log(a.name); // Jack
类的静态方法:
使用
static修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用
class Animal {constructor(name) {this.name = name;}get name() {return 'Jack';}set name(value) {console.log('setter: ' + value);}}let a = new Animal('Kitty'); // setter: Kittya.name = 'Tom'; // setter: Tomconsole.log(a.name); // Jack
五、类与接口
接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述
类实现接口
一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用
implements关键字来实现
interface Alarm {alert();}class Door {}class SecurityDoor extends Door implements Alarm {alert() {console.log('SecurityDoor alert');}}class Car implements Alarm {alert() {console.log('Car alert');}}
一个类可以实现多个接口
interface Alarm {alert();}interface Light {lightOn();lightOff();}class Car implements Alarm, Light {alert() {console.log('Car alert');}lightOn() {console.log('Car light on');}lightOff() {console.log('Car light off');}}
接口继承接口
使用
extends关键字实现接口 继承接口
interface Alarm {alert();}interface LightableAlarm extends Alarm {lightOn();lightOff();}
接口继承类
接口也可以继承类
class Point {x: number;y: number;}interface Point3d extends Point {z: number;}let point3d: Point3d = {x: 1, y: 2, z: 3};
六、泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
function createArray(length: number, value: any): Array<any> {let result = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;}createArray<string>(3, 'x'); // ['x', 'x', 'x']
- 上面代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型
- Array 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 value的类型。
单个类型参数
function createArray<T>(length: number, value: T): Array<T> {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;}createArray<string>(3, 'x'); // ['x', 'x', 'x']
上例中,我们在函数名后添加了 ,其中 T 用来指代任意输入的类型,在后面的输入 value: T 和输出 Array 中即可使用了。
多个类型参数
定义泛型的时候,可以一次定义多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] {return [tuple[1], tuple[0]];}swap([7, 'seven']); // ['seven', 7]
泛型的约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法。
interface Lengthwise {length: number;}function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length);return arg;}
上栗中使用接口对泛型进行约束,只允许这个函数传入那些包含 **
length** 属性的变量。这就是泛型约束使用了
extends约束了泛型T必须符合接口Lengthwise的形状,也就是必须包含length属性。如果调用
loggingIdentity的时候,传入的arg不包含length,那么在编译阶段就会报错了
function copyFields<T extends U, U>(target: T, source: U): T {for (let id in source) {target[id] = (<T>source)[id];}return target;}let x = { a: 1, b: 2, c: 3, d: 4 };copyFields(x, { b: 10, d: 20 });
- 上栗中使用了两个类型参数,其中要求 T 继承 U, 这样就保证了 U 上不会出现 T 中不存在的字段。
泛型接口
使用含有泛型的接口来定义函数的形状
interface CreateArrayFunc {<T>(length: number, value: T): Array<T>;}let createArray: CreateArrayFunc;createArray = function<T>(length: number, value: T): Array<T> {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;}createArray(3, 'x'); // ['x', 'x', 'x']
泛型类
使用泛型定义类
class GenericNumber<T> {zeroValue: T;add: (x: T, y: T) => T;}let myGenericNumber = new GenericNumber<number>();myGenericNumber.zeroValue = 0;myGenericNumber.add = function(x, y) { return x + y; };
泛型参数的默认类型
在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。
function createArray<T = string>(length: number, value: T): Array<T> {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;}
七、声明的合并
如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型:
函数的合并
function reverse(x: number): number;function reverse(x: string): string;function reverse(x: number | string): number | string {if (typeof x === 'number') {return Number(x.toString().split('').reverse().join(''));} else if (typeof x === 'string') {return x.split('').reverse().join('');}}
接口的合并
interface Alarm {price: number;}interface Alarm {weight: number;}
相当于:
interface Alarm {price: number;weight: number;}
注意,合并的属性的类型必须是唯一的:
interface Alarm {price: number;}interface Alarm {price: string; // 类型不一致,会报错weight: number;}// 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'.
接口中方法的合并,与函数的合并一样:
interface Alarm {price: number;alert(s: string): string;}interface Alarm {weight: number;alert(s: string, n: number): string;}
相当于:
interface Alarm {price: number;weight: number;alert(s: string): string;alert(s: string, n: number): string;}
类的合并
类的合并与接口的合并规则一致。
