上一篇我们介绍不少typescript的常用类型,这一篇我们继续介绍泛型以及函数的类型 Typescript入门系列:
TypeScript入门—类型二三事(一)
TypeScript入门—类型二三事(二) TypeScript入门—高级类型 Typescript入门——接口与类
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
我们来创建一个identity函数。 这个函数会返回任何传入它的值。可以使用any来定义函数
function identity(arg: any): any {
return arg;
}
2
3
使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。
这个时候我们就可以使用泛型,我们给identity添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型,这样参数类型与返回值类型是相同
function identity<T>(arg: T): T {
return arg;
}
2
3
多个类型参数
定义泛型的时候,可以一次定义多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
2
3
4
5
泛型约束
使用泛型时,由于不知道参数的类型,因此不能随意使用参数的属性。如下:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
2
3
4
如果想要使用参数的length属性, 只要传入的类型至少包含这一属性,ts便允许使用。 为此,我们需要列出对于T的约束要求。
为此,我们定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:如果传入的参数不包含length属性便会报错
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
loggingIdentity(3); // Error, number doesn't have a .length property
loggingIdentity({length: 10, value: 3});
2
3
4
5
6
7
8
9
10
在泛型约束中使用类型参数
你可以声明一个类型参数,且它被另一个类型参数所约束。 比如,现在我们想要用属性名从对象里获取这个属性。 并且我们想要确保这个属性存在于对象 obj上,因此我们需要在这两个类型之间使用约束。
function getProperty(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
2
3
4
5
6
7
8
泛型接口
使用含有泛型的接口来定义函数的形状
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
2
3
4
5
6
7
8
9
进一步,我们可以把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型(比如: Dictionary string 而不只是Dictionary)。 这样接口里的其它成员也能知道这个参数的类型了。
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
2
3
4
5
6
7
8
9
泛型类
除了泛型接口,我们还可以创建泛型类。 注意,无法创建泛型枚举和泛型命名空间
泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。
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; };
2
3
4
5
6
7
8
泛型参数的默认类型在 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;
}
2
3
4
5
6
7
函数
函数声明
函数声明后,typescript对输入输出都做了约束,调用该函数时不能多传也不能少传参数,或者传入参数的类型与约束的形参类型不同也是不被允许的。
function sum(x: number, y: number): number {
return x + y;
}
sum(2,3) // ok
sum(2) // error
sum(2,3,4) //error
sum(2, '3') // error
2
3
4
5
6
7
函数表达式
函数表达式即是将声明的匿名函数复制给一个变量
let mySum = function (x: number, y: number): number {
return x + y;
};
// 或者手动给mySum添加类型
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
// 也可以用接口来定义函数的形状
interface sumFuc {
(x: number, y: number): number;
}
let mySum : sumFuc
mySum = function (x: number, y: number): number {
return x + y;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
函数参数
前面提到了输入多余或者少输参数都会报错,我们可以通过可选参数或者剩余参数来解决这个问题
function sum(x: number, y?: number): number {
if(y) {
return x + y;
}
return x;
}
sum(2,3) // 5
sum(2) // 2
2
3
4
5
6
7
8
当设置可选参数时,可选参数必须接在必需参数后面,可选参数后面不能再出现必需参数,如果要解决可选参数必须接在必需参数后面这个限制,我们可以利用参数默认值来解决
function sum(x: number = 2, y: number): number {
return x + y;
}
sum(3, 5) // 8
sum(undefined, 3) // 5
sum(3) // error
2
3
4
5
6
如果不确定传入参数的个数,我们可以使用剩余参数
function sum(x: number = 2, ...item: number[]): number {
items.forEach(function(item) {
x = x + item;
});
return x;
}
sum(2, 3, 4, 6, 8) // 23
2
3
4
5
6
7
函数重载
如果我们想要实现一个sum函数,当x类型为number时返回两者之和,当传入x为数组时,返回一个数组。因为需要函数接受不同的类型参数,我们可以使用联合类型来解决。
function sum(x: number | number[], y: number): number | number[] {
if (typeof x === 'number') {
return x + y;
}
else {
return x.map(function(item) {
return y + item;
});
}
}
sum([1,2],3) // [4,5]
sum(1,2) // 3
2
3
4
5
6
7
8
9
10
11
12
但是这样存在一个问题就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为数组的时候,输出也应该为数组。
那么我们可以使用函数重载来解决:重载允许一个函数接受不同数量或类型的参数时,作出不同的处理我们可以重复定义多次sum函数,最后才来实现函数。
function sum(x: number, y: number): number
function sum(x: number[], y: number): number[]
function sum(x: number | number[], y: number): number | number[] {
if (typeof x === 'number') {
return x + y;
}
else {
return x.map(function(item) {
return y + item;
});
}
}
sum([1,2],3) // [4,5]
sum(1,2) // 3
2
3
4
5
6
7
8
9
10
11
12
13
14
TypeScript会优先从最前面的函数定义开始匹配,所以多个函数定义如有包含关系,需要优先把精确的定义写在前面
函数中的this
我们来看下面的一个例子,最终打印出来undefined,因为此时speedRun中的this是指向windom,我们知道我们可以使用箭头函数改变this指向来解决这个问题,但是在ts中如果你给编译器设置了--noImplicitThis标记,便会警告你犯了一个错误,它会指出 this.speed里的this的类型为any。
let jim = {
name: 'jim',
age: 20,
speed: 50,
runspeed: function(){
return function(){
return this.speed
}
}
}
let speedRun = jim.runspeed();
let speed = speedRun() // undefined,
2
3
4
5
6
7
8
9
10
11
12
我们可以提供一个显式的 this参数。 this参数是个假的参数,它出现在参数列表的最前面,表示 this是Person类型的,而非any,这样就不会报错了。
interface Person {
name: string
age: number
speed: number
runspeed(this: Person): () => number
}
let jim : Person = {
name: 'jim',
age: 20,
speed: 50,
runspeed: function(this: Person){
return () => {
return this.speed
}
}
}
let speedRun = jim.runspeed();
let speed = speedRun() // 50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
回调函数的this
在回调函数里this报错的情况,当你将一个函数传递到某个库函数里稍后会被调用时。 因为当回调被调用的时候,它们会被当成一个普通函数调用, this将为undefined.我们来看一个例子:
interface UIElement{
addClickListener(onclick: (this:void, e:Event) => void);
}
2
3
上面的例子接口中 this:void 表明 onclick 函数应该是一个不需要 this 对象的函数。如果 onclick 指向的是一个成员函数而且函数需要使用 this 对象,那么则 onclick 需要通过箭头函数来实现。这样就不会报错,因为箭头函数不会捕获this,所以你总是可以把它们传给期望this: void的函数。
class Handler{
info:string;
onClick = (e:Event) => { this.info = e.message }
}
let h = new Handler();
uiElement.addClickListener(h.onClick);
2
3
4
5
6
缺点是每个 Handler对象都会创建一个箭头函数。 另一方面,方法只会被创建一次,添加到 Handler的原型链上。 它们在不同 Handler对象间是共享的。