TypeScript 常见类型
布尔
数字 number
字符串 string
数组
元组 let x :[string,number] = [’hello’,10]
枚举 enum
Any 不检查,直接通过编译
void 没有任何类型,只能赋值为undefined 和 null
null undefined
Never 永远不存在类型, 必会抛出异常或者根本不会有返回值
Object非原始类型
类型断言 尖括号、as
接口 正常值 1 2 3 4 5 6 7 8 9 10 interface LabelledValue { label : string; option?:string; readonly name :string; } function read (conf:LabelledValue ){} read ({label :"123." ,name :"liming" ,color :"red" })
添加 ? 为可选,可不传递
指挥检查是否存在,不会检查顺序
readonly 只读,不可改变
如果一个对象字面量存在任何“目标类型”不包含的属性时,会报错,如上面的color
函数 1 2 3 4 5 6 7 8 9 interface SearchFunc { (source : string, subString : string): boolean; } let mySearch : SearchFunc ;mySearch = function (src: string, sub: string ): boolean { let result = src.search (sub); return result > -1 ; }
函数的入参不需要和接口定义的名字一样
如果不在入参处设置类型断言,会逐个进行匹配
可索引类型 1 2 3 4 interface StringArray { [index : number]: string; readonly [name :string]: number; }
当遇到number类型的时候,需要返回string类型数据
两种索引签名,number、string
你可以将索引签名设置为只读,这样就防止了给索引赋值:
类类型 1 2 3 4 5 6 7 8 9 10 11 12 interface ClockInterface { currentTime : Date ; setTime (d : Date ); } class Clock implements ClockInterface { currentTime : Date ; setTime (d: Date ) { this .currentTime = d; } constructor (h: number, m: number ) { } }
不检查私有成员,只检查公有
接口继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface Shape { color : string; } interface PenStroke { penWidth : number; } interface Square extends Shape , PenStroke { sideLength : number; } let square = <Square >{};square.color = "blue" ; square.sideLength = 10 ; square.penWidth = 5.0 ;
混合类型
接口继承类
继承类的成员,但是不继承实现
比如类中有一个state:”fulfilled“,会继承state,但不会继承fulfilled
继承 ”继承类的接口“ 的类必须再次实现类的属性
类 修饰符
public
private
protected 可以在派生类中访问(即子类)
readonly 只读
存取器 1 2 3 4 5 6 7 8 9 10 11 class Employee { private _fullName : string; get fullName (): string { return this ._fullName ; } set fullName (newName: string ) { console .log (newName); } }
存取器要求设置为ES5或者更高,不支持降级到ECMA3
只带有get的存取器默认推断为readonly
抽象类
★★高级技巧★★ 声明类时
1 2 3 4 5 6 7 8 9 10 11 12 13 class Greeter { greeting : string; constructor (message: string ) { this .greeting = message; } greet ( ) { return "Hello, " + this .greeting ; } } let greeter : Greeter ;greeter = new Greeter ("world" ); console .log (greeter.greet ());
创建了一个构造函数,会在new创建实例的时候调用。
上面的代码被编译之后:
1 2 3 4 5 6 7 8 9 10 11 12 13 let Greeter = (function ( ) { function Greeter (message ) { this .greeting = message; } Greeter .prototype .greet = function ( ) { return "Hello, " + this .greeting ; }; return Greeter ; })(); let greeter;greeter = new Greeter ("world" ); console .log (greeter.greet ());
回顾:使用new函数会发生什么?
创建一个空对象,成为newInstance(新的实例)
如果构造函数的prototype是一个对象,那么把newInstance的原型指向prototype,否则newInstance为一个普通对象,原型为Object.prototype
使用给定参数执行构造函数,并把newInstance绑定为this上下文
如果构造函数返回非原始值,则返回值为new的结果,否则返回newInstance
函数 类型推断:
1 2 3 let myAdd : (baseValue: number, increment: number ) => number = function (x, y ) { return x + y; };
x 和 y会被自动推断为 number类型
可选参数和默认参数
使用 ?修饰 比如 name?:string
可选参数必须放到默认参数之后
剩余参数
1 2 3 function buildName (firstName: string, ...restOfName: string[] ) { return firstName + " " + restOfName.join (" " ); }
重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function pickCard (x: {suit: string; card: number; }[] ): number;function pickCard (x: number ): {suit : string; card : number; };function pickCard (x:any ): any { if (typeof x == "object" ) { let pickedCard = Math .floor (Math .random () * x.length ); return pickedCard; } else if (typeof x == "number" ) { let pickedSuit = Math .floor (x / 13 ); return { suit : suits[pickedSuit], card : x % 13 }; } }
被重载的函数不能声明方法体
只有一个方法可以声明方法体,并且重复的入参必须为any
它按照顺序查找重载列表,尝试使用第一个满足的定义。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
泛型 1 2 3 function identity<T>(arg : T): T { return arg; }
使用尖括号定义
意义:arg的类型和identity函数返回的值的类型一样
1 2 3 4 function loggingIdentity<T>(arg : T[]): T[] { console .log (arg.length ); return arg; }
泛型参数名只要在数量上和使用方式上能对应就行
泛型类 1 2 3 4 5 6 7 8 class Person <T>{ zeroValue : T; name :T; } let liu = new Person <string>();liu.name ="asdf" liu.zeroValue ="adfa"
类有两部分,静态部分和实例部分,泛型指的是实例部分的类型
泛型约束 1 2 3 4 5 6 7 8 interface Lengthwise { length : number; } function loggingIdentity<T extends Lengthwise >(arg : T): T { console .log (arg.length ); return arg; }
如果我们想获得arg的长度,可以让泛型T继承一个带有length属性的接口
如果T为number会报错,string则不会
或者
1 2 3 loggingIdentity<string>('hello' ) loggingIdentity<number>(234 ) loggingIdentity ({length : 10 })
在泛型里使用类类型 1 2 3 function create<T>(c : {new (): T; }): T { return new c (); }
在此处,new()代表一个无参的构造函数
所以此处的入参c的意义为:一个含有无参的构造函数的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function create<T>(c : {new (): T; }): T { console .log (c) return new c (); } class Anim { constructor ( ) {} } console .log (create (Anim ) instanceof Anim );
小思考题
枚举 数字枚举 1 2 3 4 5 6 7 8 9 10 11 12 13 enum Animal { Cat = 2 , Dog , Rabbit =6 , Lion } console .log (Animal .Cat === 2 )enum E { A = getSomeValue (), B, }
索引从0开始,下面的比上面的增加1
如果某一个成员不是constant值,那么下面的成员必须初始化
字符串枚举 即使用字符串进行初始化
1 2 3 4 5 6 7 enum Animal { Cat = "Cat" , Dog = "Dog1" , } console .log (Animal .Cat )
没有自增长行为,可以序列化
异构 即数字、字符串混合,不建议
联合枚举与枚举成员的类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 enum ShapeKind { Circle , Square , } interface Circle { kind : ShapeKind .Circle ; radius : number; } interface Square { kind : ShapeKind .Square ; sideLength : number; } let c : Circle = { kind : ShapeKind .Square , radius : 100 , }
枚举成员成为了类型,意义为:这里Circle接口的kind只能为ShapeKind.Circle类型
枚举类型成为了枚举成员的类型联合
★★反向映射★★ 1 2 3 4 5 enum Enum { A } let a = Enum .A ;let nameOfA = Enum [a];
★★可能会将编译为★★
1 2 3 4 5 6 var Enum ;(function (Enum ) { Enum [Enum ["A" ] = 0 ] = "A" ; })(Enum || (Enum = {})); var a = Enum .A ;var nameOfA = Enum [a];
或许添加两个输出会更加直观
1 2 3 4 5 6 7 8 var Enum ;(function (Enum ) { Enum [Enum ["A" ] = 0 ] = "A" ; })(Enum || (Enum = {})); var a = Enum .A ;var nameOfA = Enum [a]; console .log (Enum [0 ]) console .log (Enum ["A" ])
枚举类型被编译为一个Enum对象,包含了正向name→value 和反向映射 value → name , 不会为字符串枚举成员生成反向映射
例子:
1 2 3 4 5 6 7 8 enum Enum { A = 1 , B, C = 2 } console .log (Enum .B === Enum .C ) console .log (Enum [2 ])
过程
生成一个Enum对象
设置A的正向映射为1,同时设置1的反向映射为A
设置B的正向映射为2,同时设置2的反向映射为B
设置C的正向映射为2,同时设置2的反向映射为C,覆盖了原来的B
1 2 3 4 5 (function (Enum ) { Enum [Enum ["A" ] = 1 ] = "A" Enum [Enum ["B" ] = 2 ] = "B" Enum [Enum ["C" ] = 2 ] = "C" }(Enum || (Enum = {})))
类型推论 最佳通用类型 1 2 let x = [0 , 1 , null ];x : (number|null )[]
1 2 let zoo = [new Rhino (), new Elephant (), new Snake ()];(Rhino | Elephant | Snake )[]
上下文类型 1 2 3 window .onmousedown = function (mouseEvent ) { console .log (mouseEvent.button ); };
根据上文的window.onmousedown推断出mouseEvent的类型为MouseEvent
使用注解抵消上下文类恶行
类型兼容性
如果x要兼容y,y至少具有与x相同的属性
1 2 3 4 5 6 7 8 interface Named { name : string; } let x : Named ;let y = { name : 'Alice' , location : 'Seattle' };x = y;
过程:编译器递归检查 x中的每个属性,看是否在y中能找到属性名一样且类型相同的属性
比较两个函数 1 2 3 4 5 let x = (a: number ) => 0 ;let y = (b: number, s: string ) => 0 ;y = x; x = y;
理解
y赋值给x可以,因为同时传递b和s之后,b的值可以赋值给a,也满足x的条件
x赋值给y不行,因为只传递a之后,无值可用
编译时不会赋值尝试,只会根据类型进行判断
1 2 3 4 5 let x = ( ) => ({name : 'Alice' });let y = ( ) => ({name : 'Alice' , location : 'Seattle' });x = y; y = x;
把y赋值给x那么参数依然可以调用name(x中所有的值
源函数的返回值类型必须是目标函数返回值类型的子类型
函数参数双向协变 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 enum EventType { Mouse , Keyboard } interface Event { timestamp : number; } interface MouseEvent extends Event { x : number y : number } function listenEvent (eventType: EventType, handler: (n: Event) => void ) { } listenEvent (EventType .Mouse , (e: MouseEvent ) => console .log (e.x + ',' + e.y ));listenEvent (EventType .Mouse , (e: Event ) => console .log ((<MouseEvent >e).x + ',' + (<MouseEvent >e).y ));listenEvent (EventType .Mouse , <(e: Event ) => void >((e: MouseEvent ) => console .log (e.x + ',' + e.y )));
1处报错,因为MouseEvent是Event的衍生类,不能“覆盖”Event
可以传入Event,之后使用 as 或者 <> 显示判断类型
可选参数和剩余参数
可选参数 ?. ,剩余参数 …args
可选参数可以当作无限个可选参数
1 2 3 4 5 6 7 8 9 function invokeLater (args: any[], callback: (...args: any[]) => void ) { } invokeLater ([1 , 2 ], (x, y ) => console .log (x + ', ' + y));invokeLater ([1 , 2 ], (x?, y? ) => console .log (x + ', ' + y));
此时,args为x,y….
高级类型 交叉类型
多个类型合并为一个类型
Person & Serializable & Loggable ,同时有这三种类型的成员
1 2 3 4 5 6 7 8 9 10 11 12 function extend<T, U>(first : T, second : U): T & U { let result = <T & U>{}; for (let id in first) { (<any>result)[id] = (<any>first)[id]; } for (let id in second) { if (!result.hasOwnProperty(id)) { (<any>result)[id] = (<any>second)[id]; } } return result; }
联合类型
表示可以是几个类型之一
number | boolean | string 表示可以是这三个类型之一
如果一个值是联合类型,只能访问共有的成员
1 2 3 4 5 6 7 8 9 10 11 12 13 interface Bird { move () dance () } interface Fish { move () eat () } let ani : Bird | Fish ani.move () ani.dance ()
类型保护和区分类型 使用联合类型时用来区分不同类型
类型断言
1 2 3 4 5 6 7 8 let pet = getSmallPet ();if ((<Fish >pet).swim ) { (<Fish >pet).swim (); } else { (<Bird >pet).fly (); }
类型保护
1 2 3 4 5 6 7 function isFish (pet: Fish | Bird ): pet is Fish { return (<Fish >pet).swim !== undefined ; } if (isFish (pet)){ pet.swim (); }
pet is Fish :类型谓词
这样可以判断在if分支中一定是Fish类型
typeof、instanceof
可选参数和可选属性
使用了—strictNullChecks 可选参数会自动加上 | undefined
1 2 3 4 5 6 7 function f (x: number, y?: number ) { return x + (y || 0 ); } f (1 , 2 );f (1 );f (1 , undefined );f (1 , null );
类型别名 给类型起一个新的名字
不会新建一个类型
创建了一个新的名字来引用那个类型
接口类型可以是泛型
1 type Container <T> = { value : T };
在属性里引用自己
1 2 3 4 5 type Tree <T> = { value : T; left : Tree <T>; right : Tree <T>; }
☆☆接口和类型别名☆☆ interface和type
接口创建了一个新的名字,可以在其他地方使用,类型别名不创建新的名字,而是类型的引用
类型别名不能被或者extends、implements其他类型,interface对于拓展是开放的
多个同名的interface会合并(相同属性声明为不同类型时候会报错),type则会报错
type可以表示联合类型和交叉类型,interface则会报错
1 2 type Animal = "fish" | "cow" interface Animal = "fish" | "cow"
type 可以表示元组、枚举、基本类型(如 string、number、boolean)、字面量类型等,而 interface 不能。
可辨识类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 interface Square { kind : "square" ; size : number; } interface Rectangle { kind : "rectangle" ; width : number; height : number; } interface Circle { kind : "circle" ; radius : number; } interface Triangle { kind : "triangle" ; width : number } type Shape = Square | Rectangle | Circle | Triangle ; function area (s: Shape ) { switch (s.kind ) { case "square" : return s.size * s.size ; case "rectangle" : return s.height * s.width ; case "circle" : return Math .PI * s.radius ** 2 ; default : return assertNever (s) } }
索引类型 让编译器可以检查使用了动态属性名的代码
1 2 3 4 5 interface Person { name?: string } let personProps : keyof Person = "age"
personProps是Person中的一个属性,
type、interface、class都可以使用
映射类型 1 2 3 4 5 6 type Readonly <T> = { readonly [P in keyof T]: T[P]; } type Partial <T> = { [P in keyof T]?: T[P]; }
keyof T表示T中所有属性名的联合类型
P in keyof T 类型映射,遍历T的所有属性名,为每个属性名P创建一个新的类型
T[P] 表示属性P在T中的类型
readonly 表示这个属性是只读的
Symbol 一些内置的Symbol:
Symbol.hasIntance 会被instanceof运算符调用,构造器对象用来识别一个对象是否是实例
1 2 3 4 5 6 7 8 class MyObject { static [Symbol .hasInstance ](instance) { return Array .isArray (instance); } } console .log ([] instanceof MyObject ); console .log ({} instanceof MyObject );
Symbol.iterator
1 2 3 4 5 6 7 8 class MyObject { static [Symbol .hasInstance ](instance) { return Array .isArray (instance); } } console .log ([] instanceof MyObject ); console .log ({} instanceof MyObject );
Symbol.toPrimitive
1 被ToPrimitive 抽象调用,对象转化为相应的原始值
声明合并 编译器将针对同一个名字的两个独立生命合并为单一声明
ts声明会创建:命名空间、类型、值。
接口
非函数成员应该是唯一的
同名函数会被当成重载,在后面声明的接口优先级更高(出现在靠前的位置)
出现特殊的函数签名的时候,即,有一个参数是单一的字符串字面量,会被提升到最顶端
命名空间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 namespace Animals { export class Zebra { } } namespace Animals { export interface Legged { numberOfLegs : number; } export class Dog { } } namespace Animals { export interface Legged { numberOfLegs : number; } export class Zebra { } export class Dog { } }
模块扩展 1 2 3 4 5 6 7 8 9 10 export class Observable <T> { } import { Observable } from "./observable" ;Observable .prototype .map = function (f ) { }
编译器不会进行提示,通知编译器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { Observable } from "./observable" ;declare module "./observable" { interface Observable <T> { map<U>(f : (x: T ) => U): Observable <U>; } } Observable .prototype .map = function (f ) { } import { Observable } from "./observable" ;import "./map" ;let o : Observable <number>;o.map (x => x.toFixed ());