TypeScript函数

针对函数的部分,做了如下的回顾

函数类型表达式

用于描述函数参数类型、返回值。

1
2
3
4
5
6
7
8
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
// 也可以用别名
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
// ...
}

调用签名

用于用属性描述函数可调用的东西,可以在对象类型中编写调用签名

1
2
3
4
5
6
7
type DescribableFunction = {
description: string;
(someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6));
}

与函数类型表达式相比,在参数列表和返回值之间使用了:而不是=>

构造签名

通过在调用签名前添加关键字new来编写构造签名

1
2
3
4
5
6
type SomeConstructor = {
new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
return new ctor("hello");
}

泛型函数

描述某一类参数、返回值类型相同的函数。当想要描述参数与返回值的关系时,可通过泛型指定

1
2
3
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0];
}

推断

ts可以自动根据参数类型推断出返回值的类型

1
2
3
4
5
6
7
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
return arr.map(func);
}

// Parameter 'n' is of type 'string'
// 'parsed' is of type 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n));

TypeScript 可以推断Input类型参数的类型(从给定的string数组),以及Output基于函数表达式的返回值的类型参数 ( number)

约束

指的就是对类型或属性的限制。在 想关联两个值,但只能对某个值的子集进行操作 时可以用到

1
2
3
4
5
6
7
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}

上述的extends就是子句,用于当我们无法直接操作Type类型的情况

还需要注意一个常见的错误:函数承诺返回与传入相同类型的对象,而不仅仅是与约束匹配的某个对象。

可选参数

通过?声明可选参数。可选参数可以接收undefined。

可以声明默认参数 fn(x=10)

回调中的可选参数

为回调编写函数类型时,切勿编写可选​​参数,除非打算在不传递该参数的情况下调用该函数

1
2
3
4
5
6
7
8
9
10
11
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
// I don't feel like providing the index today
callback(arr[i]);
}
}

myForEach([1, 2, 3], (a, i) => {
console.log(i.toFixed());
// Object is possibly 'undefined'.
});

上述报错显示i可能为undefined,但实际是不可能出现的错误

函数重载

在 TypeScript 中,我们可以通过编写重载签名来指定一个可以以不同方式调用的函数。为此,请编写一些函数签名(通常是两个或更多),然后是函数的主体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);

前两个签名称为重载签名。然后,我们编写了一个具有兼容签名的函数实现。函数有一个实现签名,但是这个签名不能直接调用。即使我们在必需的参数之后编写了一个带有两个可选参数的函数,也不能用两个参数调用它!

实现签名还必须与重载签名兼容

尽可能使用联合类型的参数而不是重载

this 在函数中声明

JavaScript 规范声明你不能有一个名为 this 的参数,因此 TypeScript 使用该语法空间让你在函数体中声明 this 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const user = {
id: 123,

admin: false,
becomeAdmin: function () {
this.admin = true;
},
};

interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}

const db = getDB();
const admins = db.filterUsers(function (this: User) {
return this.admin;
});

其他的一些类型

  • void
    无返回值
  • unknown
    表示任何值,类似与any,但比any更安全。因为用unknown做任何事情都是不合法的
  • never
    示从未观察到的值,意味着达不到的意思。在函数返回类型中,这意味着函数抛出异常或终止程序的执行
  • Function
    全局类型Function描述了 JavaScript 中所有函数值上的属性,如bind、call、apply和其他属性
  • object
    特殊类型object是指任何不是原始值(string、number、bigint、boolean、symbol、null或undefined)的值。这与空对象类型 { }不同,也与全局类型不同Object。很可能永远不会使用Object
    object不是Object。一直用object!

剩余参数和参数

rest 参数出现在所有其他参数之后,并使用…语法

1
2
3
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x);
}

通常,TypeScript 并不假定数组是不可变的。即展开运算符扩展的是一个以上的参数,而不是具体的几个参数

1
2
3
const args = [8, 5];
const angle = Math.atan2(...args);
// A spread argument must either have a tuple type or be passed to a rest parameter.

错误提示需要两个参数,正在用“0或更多”参数调用Math.atan2()。
这种情况的最佳解决方案取决于代码,但一般来说,const上下文是最直接的解决方案

1
2
3
4
// Inferred as 2-length tuple
const args = [8, 5] as const;
// OK
const angle = Math.atan2(...args);

通过使用as const,将args数组变为了定长的只读数组,防止了报错

参数解构

1
2
3
4
5

function sum({ a, b, c }: { a: number; b: number; c: number }) {
console.log(a + b + c);
}

作者

徐云飞

发布于

2022-09-12

更新于

2023-02-05

许可协议

评论