使用双重断言,先断言成兼容所有类型的 any
,再断言为别的类型可以避免编译器将不会报错:const element = (event as any) as HTMLElement
null
和undefined
是所有类型的子类型。 就是说可以把 null
和undefined
赋值给number
类型的变量
类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以
对象字面量会被特殊对待而且会经过 额外属性检查,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,会得到一个错误
TypeScript支持两种索引签名:字符串和数字
当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。也就是用 100(一个number)去索引等同于使用”100”(一个string)去索引,因此两者需要保持一致
接口描述了实现类的公共部分,而不是公共和私有两部分;不会帮你检查类是否具有某些私有成员
类类型的只是很重要
默认为 public 类的外部可访问
private类的外部不能访问
protected 在派生类(继承类)内部可以访问
构造函数也可以被标记成 protected
,这意味着这个类不能在包含它的类外被实例化(不能 new),但是能被继承
返回值类型是函数类型的必要部分,如果函数没有返回任何值,也必须指定返回值类型为 void而不能不写。
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个;编译器创建参数数组,名字是在省略号(…)后面给定的名字,可以在函数体内使用这个数组
泛型:在调用函数时确定入参与出参的类型,达到返回值的类型与传入参数的类型相同,的目的
绕开额外的属性检查:
1 2 3 4 5 interface SquareConfig { color?: string; width?: number; [propName: string]: any; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 interface SquareConfig { color?: string ; width?: number ; } function createSquare (config: SquareConfig ): {color: string ; area: number } { let newSquare = {color: "white" , area: 100 }; if (config.color) { newSquare.color = config.color; } if (config.width) { newSquare.area = config.width * config.width; } return newSquare; } let mySquare = createSquare({color: "black" });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function identity <T >(arg: T ): T { return arg; } let output = identity<string>("myString" ); let output = identity("myString" ); function loggingIdentity<T>(arg: T[]): T[] { console .log(arg.length); return arg; } function loggingIdentity <T >(arg: Array<T> ): Array <T > { console .log(arg.length); return arg; }
1 2 3 4 5 6 7 8 9 10 11 12 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 5 6 7 8 9 interface Foo { bar: number; bas: string; } const foo = {} as Foo;foo.bar = 123 ; foo.bas = 'hello' ;
基础类型 布尔值、数字、字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 let isDone: boolean = false ;let decLiteral: number = 6 ;let hexLiteral: number = 0xf00d ;let binaryLiteral: number = 0b1010 ;let octalLiteral: number = 0o744 ;let name: string = "bob" ;name = "smith" ; let name: string = `Gene` ;let age: number = 37 ;let sentence: string = `Hello, my name is ${ name } `
数组 1 2 3 4 5 let list: number [] = [1 , 2 , 3 ];let list: Array <number > = [1 , 2 , 3 ];
元组 Tuple
允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
1 2 3 4 5 let x: [string , number ];x = ['hello' , 10 ]; x = [10 , 'hello' ];
当访问一个越界的元素,会使用联合类型替代
1 2 3 4 5 x[3 ] = 'world' ; console .log(x[5 ].toString()); x[6 ] = true ;
枚举 enum
使用枚举类型可以为一组数值赋予友好的名字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 enum Color {Red, Green, Blue}let c: Color = Color.Green; enum Color {Red = 1 , Green, Blue}let c: Color = Color.Green; enum Color {Red = 1 , Green = 2 , Blue = 4 }let c: Color = Color.Green; enum Color {Red = 1 , Green, Blue}let colorName: string = Color[2 ];console .log(colorName);
任何类型 Any
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let notSure: any = 4 ;notSure = "maybe a string instead" ; notSure = false ; let notSure: any = 4 ;notSure.ifItExists(); notSure.toFixed(); let prettySure: Object = 4 ;prettySure.toFixed(); let list: any [] = [1 , true , "free" ];list[1 ] = 100 ;
Void
当一个函数没有返回值时1 2 3 4 5 function warnUser ( ): void { console .log("This is my warning message" ); } let unusable: void = undefined ;
Null
和 Undefined
null
和undefined
是所有类型的子类型。 就是说可以把 null
和undefined
赋值给number
类型的变量
当指定了--strictNullChecks
标记,null
和undefined
只能赋值给void
和它们各自
1 2 3 let u: undefined = undefined ;let n: null = null ;
never
永不存在的值的类型
never
类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
never
类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never
的子类型或可以赋值给never
类型(除了never
本身之外)。 即使 any
也不可以赋值给never
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function error (message: string ): never { throw new Error (message); } function fail ( ) { return error("Something failed" ); } function infiniteLoop ( ): never { while (true ) { } }
Object
object
表示非原始类型,也就是除number
,string
,boolean
,symbol
,null
或undefined
之外的类型
1 2 3 4 5 6 7 8 9 10 declare function create (o: object | null ): void ;create({ prop: 0 }); create(null ); create(42 ); create("string" ); create(false ); create(undefined );
类型断言
TypeScript
允许你覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」
断言能让你更容易的从遗留项目中迁移
类型断言的一个常见用例是当从 JavaScript
迁移到 TypeScript
时:
1 2 3 const foo = {};foo.bar = 123 ; foo.bas = 'hello' ;
发出了错误警告,因为foo
的类型推断为 {}
,即具有零属性的对象
。因此,不能在它的属性上添加 bar
或 bas
,可以通过类型断言来避免此问题:
1 2 3 4 5 6 7 8 interface Foo { bar: number ; bas: string ; } const foo = {} as Foo;foo.bar = 123 ; foo.bas = 'hello' ;
as foo
与 <foo>
1 2 let foo: any ;let bar = <string >foo;
JSX
中使用 <foo>
的断言语法时,这会与JSX
的语法存在歧义,因此,为了一致性,建议使用 as foo
的语法来为类型断言
双重断言 1 2 3 function handler (event: Event ) { const mouseEvent = event as MouseEvent; }
然而,如下例子中的代码将会报错,尽管使用者已经使用了类型断言:
1 2 3 function handler (event: Event ) { const element = event as HTMLElement; }
可以使用双重断言。首先断言成兼容所有类型的 any,编译器将不会报错:
1 2 3 function handler (event: Event ) { const element = (event as any ) as HTMLElement; }
TypeScript 是怎么确定单个断言是否足够 当 S
类型是 T
类型的子集
,或者 T
类型是 S
类型的子集
时,S
能被成功断言成 T
。
解构 解构数组 1 2 3 4 5 6 7 let input = [1 , 2 ];function f ([first, second]: [number , number ] ) { console .log(first); console .log(second); } f(input);
对象解构 属性重命名 1 2 3 4 5 6 7 8 let o = { a: "foo" , b: 12 , c: "bar" }; let { a: newName1, b: newName2 } = o;let {a, b}: {a: string , b: number } = o;
默认值 1 2 3 function keepWholeObject (wholeObject: { a: string , b?: number } ) { let { a, b = 1001 } = wholeObject; }
接口 interface
TypeScript
的核心原则之一是对值所具有的结构进行类型检查。
接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约
使用接口来描述:必须包含一个label属性且类型为string:
1 2 3 4 5 6 7 8 9 10 interface LabelledValue { label: string ; } function printLabel (labelledObj: LabelledValue ) { console .log(labelledObj.label); } let myObj = {size: 10 , label: "Size 10 Object" };printLabel(myObj);
LabelledValue
接口代表了有一个 label
属性且类型为string
的对象
只要传入的对象满足上面提到的必要条件,那么它就是被允许的。
还有一点值得提的是,类型检查器不会去检查属性的顺序
,只要相应的属性存在
并且类型也是对的
就可以
传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性
是否存在,并且其类型是否匹配
可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?
符号
在应用“option bags”
模式时很常用,即给函数传入的参数对象中只有部分属性赋值了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface SquareConfig { color?: string ; width?: number ; } function createSquare (config: SquareConfig ): {color: string ; area: number } { let newSquare = {color: "white" , area: 100 }; if (config.color) { newSquare.color = config.color; } if (config.width) { newSquare.area = config.width * config.width; } return newSquare; } let mySquare = createSquare({color: "black" });
好处 1、可以对可能存在的属性进行预定义 2、可以捕获引用了不存在的属性时的错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface SquareConfig { color?: string ; width?: number ; } function createSquare (config: SquareConfig ): { color: string ; area: number } { let newSquare = {color: "white" , area: 100 }; if (config.clor) { newSquare.color = config.clor; } if (config.width) { newSquare.area = config.width * config.width; } return newSquare; } let mySquare = createSquare({color: "black" });
只读属性 一些对象属性只能在对象刚刚创建的时候修改其值。 在属性名前用 readonly
来指定只读属性:
1 2 3 4 interface Point { readonly x: number ; readonly y: number ; }
通过赋值一个对象字面量来构造一个Point。 赋值后, x和y再也不能被改变了
1 2 let p1: Point = { x: 10 , y: 20 };p1.x = 5 ;
TypeScript
具有ReadonlyArray<T>
类型,它与Array<T>
相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改
1 2 3 4 5 6 let a: number [] = [1 , 2 , 3 , 4 ];let ro: ReadonlyArray<number > = a;ro[0 ] = 12 ; ro.push(5 ); ro.length = 100 ; a = ro;
就算把整个ReadonlyArray赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写:
readonly
vs const
做为变量使用的话用 const
,若做为属性则使用readonly
。
额外的属性检查 对象字面量
会被特殊对待而且会经过 额外属性检查
,当将它们赋值给变量
或作为参数传递
的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性
时,会得到一个错误。
1 2 3 4 5 6 7 8 9 10 11 interface SquareConfig { color?: string ; width?: number ; } function createSquare (config: SquareConfig ): { color: string ; area: number } { } let mySquare = createSquare({ colour: "red" , width: 100 });
绕开额外的属性检查 1、使用类型断言
1 let mySquare = createSquare({ width: 100 , opacity: 0.5 } as SquareConfig);
2、(最佳的方式)
添加一个字符串索引签名
SquareConfig带有上面定义的类型的color和width属性,并且还会带有任意数量的其它属性[propName: string]: any;
1 2 3 4 5 interface SquareConfig { color?: string ; width?: number ; [propName: string ]: any ; }
3、将这个对象赋值给一个另一个变量: 因为 squareOptions
不会经过额外属性检查,所以编译器不会报错。
1 2 let squareOptions = { colour: "red" , width: 100 };let mySquare = createSquare(squareOptions);
对于包含方法和内部状态的复杂对象字面量
来讲,可能需要使用这些技巧,但是大部额外属性检查错误是真正的bug
。 就是说遇到了额外类型检查出的错误,比如“option bags”
,应该去审查一下类型声明
函数类型
接口也可以描述函数类型,需要给接口定义一个调用签名。 就像是一个只有参数列表
和返回值类型
的函数定义。参数列表里的每个参数都需要名字和类型。
1 2 3 interface SearchFunc { (source: string , subString: string ): boolean ; }
何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量:
1 2 3 4 5 let mySearch: SearchFunc;mySearch = function (source: string , subString: string ) { let result = source.search(subString); return result > -1 ; }
对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配
1 2 3 4 5 let mySearch: SearchFunc;mySearch = function (src: string , sub: string ): boolean { let result = src.search(sub); return result > -1 ; }
可索引的类型 接口可以描述那些能够“通过索引得到”
的类型,比如a[10]
或ageMap["daniel"]
。 可索引类型
具有一个 索引签名
,它描述了对象索引的类型
,还有相应的索引返回值类型
。
1 2 3 4 5 6 7 8 interface StringArray { [index: number ]: string ; } let myArray: StringArray;myArray = ["Bob" , "Fred" ]; let myStr: string = myArray[0 ];
TypeScript
支持两种索引签名:字符串
和数字
可以同时使用
两种类型的索引,但是数字索引返回值
必须是字符串索引返回值
类型的子类型
因为当使用 number
来索引时,JavaScript
会将它转换成string
然后再去索引对象。 也就是说用 100
(一个number)去索引等同于使用"100"
(一个string)去索引,因此两者需要保持一致
1 2 3 4 5 6 7 8 9 10 11 12 class Animal { name: string ; } class Dog extends Animal { breed: string ; } interface NotOkay { [x: number ]: Animal; [x: string ]: Dog; }
可以将索引签名设置为只读,这样就防止了给索引赋值
1 2 3 4 5 interface ReadonlyStringArray { readonly [index: number ]: string ; } let myArray: ReadonlyStringArray = ["Alice" , "Bob" ];myArray[2 ] = "Mallory" ;
类类型(先定义 后实现)
实现的时候 必须 先要通过一个类来实现(implements
)这个接口(interface )然后在这个类里边进行实现
接口描述了实现类的公共部分,而不是公共和私有两部分;不会帮你检查类是否具有某些私有成员
属性的定义和实现 implements
1 2 3 4 5 6 7 8 interface ClockInterface { currentTime: Date ; } class Clock implements ClockInterface { currentTime: Date ; constructor (h: number , m: number ) { } }
方法的定义和实现 implements
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 interface Shape { color: string ; } interface Square extends Shape { sideLength: number ; } let square = <Square>{};square.color = "blue" ; square.sideLength = 10 ;
一个接口可以继承多个接口,创建出多个接口的合成接口
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 ;
混合类型:同时具有上面提到的多种类型 例子:一个对象可以同时做为函数和对象使用,并带有额外的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface Counter { (start: number ): string ; interval: number ; reset(): void ; } function getCounter ( ): Counter { let counter = <Counter>function (start: number ) { }; counter.interval = 123 ; counter.reset = function ( ) { }; return counter; } let c = getCounter();c(10 ); c.reset(); c.interval = 5.0 ;
在使用JavaScript第三方库的时候,可能需要像上面那样去完整地定义类型
接口继承类 类 1 2 3 4 5 6 7 8 9 10 11 class Greeter { greeting: string ; constructor (message: string ) { this .greeting = message; } greet() { return "Hello, " + this .greeting; } } let greeter = new Greeter("world" );
继承
简单示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Animal { move(distanceInMeters: number = 0 ) { console .log(`Animal moved ${distanceInMeters} m.` ); } } class Dog extends Animal { bark() { console .log('Woof! Woof!' ); } } const dog = new Dog();dog.bark(); dog.move(10 ); dog.bark();
复杂的例子
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 27 28 29 30 31 32 33 34 class Animal { name: string ; constructor (theName: string ) { this .name = theName; } move(distanceInMeters: number = 0 ) { console .log(`${this .name} moved ${distanceInMeters} m.` ); } } class Snake extends Animal { constructor (name: string ) { super (name); } move(distanceInMeters = 5 ) { console .log("Slithering..." ); super .move(distanceInMeters); } } class Horse extends Animal { constructor (name: string ) { super (name); } move(distanceInMeters = 45 ) { console .log("Galloping..." ); super .move(distanceInMeters); } } let sam = new Snake("Sammy the Python" );let tom: Animal = new Horse("Tommy the Palomino" );sam.move(); tom.move(34 );
公共,私有与受保护的修饰符 默认为 public
类的外部可访问 1 2 3 4 5 6 7 class Animal { public name: string ; public constructor (theName: string ) { this .name = theName; } public move(distanceInMeters: number ) { console .log(`${this .name} moved ${distanceInMeters} m.` ); } }
private
类的外部不能访问1 2 3 4 5 6 class Animal { private name: string ; constructor (theName: string ) { this .name = theName; } } new Animal("Cat" ).name;
当比较带有 private
或 protected
成员的类型的时候,如果其中一个类型里包含一个 private
成员,那么只有当另外一个类型中也存在这样一个 private
成员, 并且它们都是来自同一处声明时,才认为这两个类型是兼容的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Animal { private name: string ; constructor (theName: string ) { this .name = theName; } } class Rhino extends Animal { constructor ( ) { super ("Rhino" ); } } class Employee { private name: string ; constructor (theName: string ) { this .name = theName; } } let animal = new Animal("Goat" );let rhino = new Rhino();let employee = new Employee("Bob" );animal = rhino; animal = employee;
这个例子中Rhino
是 Animal
类的子类, Employee
类,其类型看上去与 Animal
是相同的。 因为 Animal
和 Rhino
共享了来自 Animal
里的私有成员定义 private name: string
,因此它们是兼容的。 然而 当把 Employee
赋值给 Animal
的时候,得到一个错误,说它们的类型不兼容。 尽管 Employee
里也有一个私有成员 name
,但它明显不是 Animal
里面定义的那个。
protected
在派生类中可以访问protected
修饰符与 private
修饰符的行为很相似,但有一点不同, protected
成员在派生类中
仍然可以访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Person { protected name: string ; constructor (name: string ) { this .name = name; } } class Employee extends Person { private department: string ; constructor (name: string , department: string ) { super (name) this .department = department; } public getElevatorPitch() { return `Hello, my name is ${this .name} and I work in ${this .department} .` ; } } let howard = new Employee("Howard" , "Sales" );console .log(howard.getElevatorPitch()); console .log(howard.name);
不能在 Person
类外使用 name
,但是可以通过 Employee
类的实例的方法
访问,因为 Employee
是由 Person
派生而来的。
构造函数也可以被标记成 protected
这意味着这个类不能在包含它的类外被实例化,但是能被继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Person { protected name: string ; protected constructor (theName: string ) { this .name = theName; } } class Employee extends Person { private department: string ; constructor (name: string , department: string ) { super (name); this .department = department; } public getElevatorPitch() { return `Hello, my name is ${this .name} and I work in ${this .department} .` ; } } let howard = new Employee("Howard" , "Sales" );let john = new Person("John" );
readonly
修饰符将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化
1 2 3 4 5 6 7 8 9 class Octopus { readonly name: string ; readonly numberOfLegs: number = 8 ; constructor (theName: string ) { this .name = theName; } } let dad = new Octopus("Man with the 8 strong legs" );dad.name = "Man with the 3-piece suit" ;
参数属性 1 2 3 4 5 class Octopus { readonly numberOfLegs: number = 8 ; constructor (readonly name: string ) { } }
存取器 (跟es6一样) 通过getters/setters
来截取对对象成员的访问
只带有 get
不带有 set
的存取器自动被推断为 readonly
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 let passcode = "secret passcode" ;class Employee { private _fullName: string ; get fullName(): string { return this ._fullName; } set fullName(newName: string ) { if (passcode && passcode == "secret passcode" ) { this ._fullName = newName; } else { console .log("Error: Unauthorized update of employee!" ); } } } let employee = new Employee();employee.fullName = "Bob Smith" ; if (employee.fullName) { alert(employee.fullName); }
静态属性 每个实例想要访问这个属性的时候,都要在 origin
前面加上类名
。 如同在实例属性上使用 this.
前缀来访问属性一样,这里我们使用 Grid.
来访问静态属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Grid { static origin = {x: 0 , y: 0 }; calculateDistanceFromOrigin(point: {x: number ; y: number ;}) { let xDist = (point.x - Grid.origin.x); let yDist = (point.y - Grid.origin.y); return Math .sqrt(xDist * xDist + yDist * yDist) / this .scale; } constructor (public scale: number ) { } } let grid1 = new Grid(1.0 ); let grid2 = new Grid(5.0 ); console .log(grid1.calculateDistanceFromOrigin({x: 10 , y: 10 }));console .log(grid2.calculateDistanceFromOrigin({x: 10 , y: 10 }));
抽象类 (也是先定义 然后再子类中实现) 抽象类做为其它派生类的基类使用。 抽象类一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节
。abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法。
1 2 3 4 5 6 abstract class Animal { abstract makeSound(): void ; move(): void { console .log('roaming the earch...' ); } }
先定义 然后再子类中实现 抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体
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 27 28 29 30 31 32 33 abstract class Department { constructor (public name: string ) { } printName(): void { console .log('Department name: ' + this .name); } abstract printMeeting(): void ; } class AccountingDepartment extends Department { constructor ( ) { super ('Accounting and Auditing' ); } printMeeting(): void { console .log('The Accounting Department meets each Monday at 10am.' ); } generateReports(): void { console .log('Generating accounting reports...' ); } } let department: Department; department = new Department(); department = new AccountingDepartment(); department.printName(); department.printMeeting(); department.generateReports();
函数 函数类型 为函数定义类型
给每个参数添加类型之后再为函数本身添加返回值类型
函数类型包含两部分:参数类型和返回值类型。 当写出完整函数类型的时候,这两部分都是需要的
只要参数类型是匹配的,那么就认为它是有效的函数类型,而不在乎参数名是否正确
返回值类型是函数类型的必要部分,如果函数没有返回任何值,也必须指定返回值类型为 void
而不能不写
。
1 2 3 4 5 6 7 8 9 10 11 12 13 function add (x: number , y: number ): number { return x + y; } let myAdd = function (x: number , y: number ): number { return x + y; };let myAdd: (x: number , y: number ) => number = function (x: number , y: number ): number { return x + y; }; let myAdd: (baseValue: number , increment: number ) => number = function (x: number , y: number ): number { return x + y; };
推断类型 如果你在赋值语句的一边指定了类型但是另一边没有类型的话,TypeScript编译器会自动识别出类型:
1 2 3 4 5 6 let myAdd = function (x: number , y: number ): number { return x + y; };let myAdd: (baseValue: number , increment: number ) => number = function (x, y ) { return x + y; };
可选参数和默认参数 传递给一个函数的参数个数必须与函数期望的参数个数一致 1 2 3 4 5 6 7 function buildName (firstName: string , lastName: string ) { return firstName + " " + lastName; } let result1 = buildName("Bob" ); let result2 = buildName("Bob" , "Adams" , "Sr." ); let result3 = buildName("Bob" , "Adams" );
?
实现可选参数的功能
JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined。 在TypeScript里我们可以在参数名旁使用 ?
实现可选参数的功能
可选参数必须跟在必须参数后面
1 2 3 4 5 6 7 8 9 10 function buildName (firstName: string , lastName?: string ) { if (lastName) return firstName + " " + lastName; else return firstName; } let result1 = buildName("Bob" ); let result2 = buildName("Bob" , "Adams" , "Sr." ); let result3 = buildName("Bob" , "Adams" );
为参数提供一个默认值 1 2 3 4 5 6 7 8 function buildName (firstName: string , lastName = "Smith" ) { return firstName + " " + lastName; } let result1 = buildName("Bob" ); let result2 = buildName("Bob" , undefined ); let result3 = buildName("Bob" , "Adams" , "Sr." ); let result4 = buildName("Bob" , "Adams" );
可选参数与末尾的默认参数共享参数类型 在所有必须参数后面的带默认初始化的参数都是可选的,与可选参数一样,在调用函数的时候可以省略。 也就是说可选参数与末尾的默认参数共享参数类型
1 2 3 4 5 6 7 function buildName (firstName: string , lastName?: string ) { } function buildName (firstName: string , lastName = "Smith" ) { }
带默认值的参数不需要放在必须参数的后面
如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined
值来获得默认值
1 2 3 4 5 6 7 8 function buildName (firstName = "Will", lastName: string ) { return firstName + " " + lastName; } let result1 = buildName("Bob" ); let result2 = buildName("Bob" , "Adams" , "Sr." ); let result3 = buildName("Bob" , "Adams" ); let result4 = buildName(undefined , "Adams" );
剩余参数
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。
编译器创建参数数组,名字是在省略号(...
)后面给定的名字,可以在函数体内使用这个数组。
1 2 3 4 5 function buildName (firstName: string , ...restOfName: string [] ) { return firstName + " " + restOfName.join(" " ); } let employeeName = buildName("Joseph" , "Samuel" , "Lucas" , "MacKinzie" );
1 2 3 4 5 function buildName (firstName: string , ...restOfName: string [] ) { return firstName + " " + restOfName.join(" " ); } let buildNameFun: (fname: string , ...rest: string [] ) => string = buildName;
this 在js非严格模式下this: 1、自执行函数中的this一般都是window
2、给元素的某个事件绑定方法,当事件触发执行对应事件的时候,方法中的this
一般都是元素本身 3、还有一种方式能快速区分this
:当方法执行的时候,看方法前边是否有 .
有 .
, .
前边是谁this
就是谁,没有 .
this一般就是window
在js严格模式下this: 如果执行主体不明确,this
指向的是undefined
(非严格模式下指向的是window)
最好是传递 this参数
提供一个显式的 this
参数,this
参数是个假的参数,它出现在参数列表的最前面
1 2 3 function f (this : void ) { }
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 27 interface Card { suit: string ; card: number ; } interface Deck { suits: string []; cards: number []; createCardPicker(this : Deck): () => Card; } let deck: Deck = { suits: ["hearts" , "spades" , "clubs" , "diamonds" ], cards: Array (52 ), createCardPicker: function (this : Deck ) { return () => { let pickedCard = Math .floor(Math .random() * 52 ); let pickedSuit = Math .floor(pickedCard / 13 ); return {suit: this .suits[pickedSuit], card: pickedCard % 13 }; } } } let cardPicker = deck.createCardPicker();let pickedCard = cardPicker();alert("card: " + pickedCard.card + " of " + pickedCard.suit);
现在TypeScript
知道createCardPicker
期望在某个Deck
对象上调用。 也就是说 this
是Deck类型
的,而非any
,因此--noImplicitThis
不会报错了
重载 JavaScript
里函数根据传入不同的参数而返回不同类型的数据是很常见的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 let suits = ["hearts" , "spades" , "clubs" , "diamonds" ]; function pickCard (x ): 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 }; } } let myDeck = [{ suit: "diamonds" , card: 2 }, { suit: "spades" , card: 10 }, { suit: "hearts" , card: 4 }];let pickedCard1 = myDeck[pickCard(myDeck)];alert("card: " + pickedCard1.card + " of " + pickedCard1.suit); let pickedCard2 = pickCard(15 );alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
pickCard
方法根据传入参数的不同会返回两种不同的类型。 如果传入的是代表纸牌的对象,函数作用是从中抓一张牌。 如果用户想抓牌,我们告诉他抓到了什么牌。
在类型系统里表示 为同一个函数
提供多个函数类型定义
来进行函数重载。 编译器会根据这个列表去处理函数的调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 let suits = ["hearts" , "spades" , "clubs" , "diamonds" ];function pickCard (x: {suit: string ; card: number ; }[] ): number ; function pickCard (x: number ): {suit: string ; card: number ; }; function pickCard (x ): 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 }; } } let myDeck = [{ suit: "diamonds" , card: 2 }, { suit: "spades" , card: 10 }, { suit: "hearts" , card: 4 }];let pickedCard1 = myDeck[pickCard(myDeck)];alert("card: " + pickedCard1.card + " of " + pickedCard1.suit); let pickedCard2 = pickCard(15 );alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
重载的pickCard
函数在调用的时候会自动进行正确的类型检查编译器
查找重载列表
时,尝试使用第一个重载定义。 如果匹配的话就使用这个不匹配查找下一个,因此,在定义重载
的时候,一定要把最精确
的定义放在最前面
泛型
在调用函数时确定入参与出参的类型
达到返回值的类型与传入参数的类型相同,的目的
使用泛型
来创建可重用的组件
,一个组件可以支持多种类型的数据
。 这样用户就可以以自己的数据类型来使用组件。
泛型之Hello World 1 2 3 4 5 6 7 8 function identity (arg: number ): number { return arg; } function identity (arg: any ): any { return arg; }
当我们需要传入的类型与返回的类型相同的时候。我们可以发现any的一个不足:上边的代码,当传入一个数字时,可以返回任何类型的值
需要一种方法使返回值的类型
与传入参数的类型
是相同
的时候,我们使用泛型
1 2 3 function identity <T >(arg: T ): T { return arg; }
泛型函数两种使用方法 第一种是,传入所有的参数,包含类型参数
1 let output = identity<string >("myString" );
第二种利用了类型推论 – 即编译器会根据传入的参数自动地帮助我们确定T的类型
1 let output = identity("myString" );
没必要使用尖括号(<>
)来明确地传入类型;编译器可以查看myString
的值,然后把T
设置为它的类型。 类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型
使用泛型变量 使用泛型创建像identity
这样的泛型函数时,必须把这些参数当做是任意或所有类型 泛型函数的参数可能是任意类型,所以我们没办法使用特定类型才有方法
1 2 3 4 5 function loggingIdentity <T >(arg: T ): T { console .log(arg.length); return arg; }
假设我们想操作T类型的数组
而不直接是T
。由于我们操作的是数组,所以.length
属性是应该存在的。
1 2 3 4 5 6 7 8 9 10 11 function loggingIdentity <T >(arg: T[] ): T [] { console .log(arg.length); return arg; } function loggingIdentity <T >(arg: Array <T> ): Array <T > { console .log(arg.length); return arg; }
泛型类型 泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样:
1 2 3 4 5 function identity <T >(arg: T ): T { return arg; } let myIdentity: <T>(arg: T ) => T = identity;
可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以
1 2 3 4 5 function identity <T >(arg: T ): T { return arg; } let myIdentity: <U>(arg: U ) => U = identity;
可以使用带有调用签名的对象字面量来定义泛型函数:
1 2 3 4 5 function identity <T >(arg: T ): T { return arg; } let myIdentity: {<T>(arg: T): T} = identity;
泛型接口
1 2 3 4 5 6 7 8 9 interface GenericIdentityFn { <T>(arg: T): T; } function identity <T >(arg: T ): T { return arg; } let myIdentity: GenericIdentityFn = identity;
把泛型参数当作整个接口的一个参数
,能清楚的知道使用的具体是哪个泛型类型(比如: Dictionary<string>
而不只是Dictionary
)。 这样接口里的其它成员也能知道这个参数的类型了。
1 2 3 4 5 6 7 8 9 interface GenericIdentityFn<T> { (arg: T): T; } function identity <T >(arg: T ): T { return arg; } let myIdentity: GenericIdentityFn<number > = identity;
泛型类 泛型类使用( <>
)括起泛型类型,跟在类名后面 使用时再规定类型 类有两部分:静态部分
和实例部分
。 泛型类
指的是实例部分的类型
,所以类的静态属性不能使用
这个泛型类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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; }; let stringNumeric = new GenericNumber<string >();stringNumeric.zeroValue = "" ; stringNumeric.add = function (x, y ) { return x + y; }; console .log(stringNumeric.add(stringNumeric.zeroValue, "test" ));
泛型约束 定义一个接口来描述约束条件。 创建一个包含 .length
属性的接口,使用这个接口和extends
关键字来实现约束:
1 2 3 4 5 6 7 8 9 10 11 12 interface Lengthwise { length: number ; } function loggingIdentity <T extends Lengthwise >(arg: T ): T { console .log(arg.length); return arg; } loggingIdentity(3 ); loggingIdentity({length: 10 , value: 3 });
在泛型约束中使用类型参数 可以声明一个类型参数,且它被另一个类型参数所约束
1 2 3 4 5 6 7 8 9 function getProperty (obj: T, key: K ) { return obj[key]; } let x = { a: 1 , b: 2 , c: 3 , d: 4 };getProperty(x, "a" ); getProperty(x, "m" );
在泛型里使用类类型 在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型
1 2 3 function create <T >(c: {new (): T; } ): T { return new c(); }
使用原型属性推断并约束构造函数与类实例的关系
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 class BeeKeeper { hasMask: boolean ; } class ZooKeeper { nametag: string ; } class Animal { numLegs: number ; } class Bee extends Animal { keeper: BeeKeeper; } class Lion extends Animal { keeper: ZooKeeper; } function createInstance <A extends Animal >(c: new () => A ): A { return new c(); } createInstance(Lion).keeper.nametag; createInstance(Bee).keeper.hasMask;