函数是任何应用程序的基本构建块,无论是本地函数、从其他模块导入的函数,还是类中的方法。它们也是值,就像其他值一样,TypeScript 提供了多种方式来描述函数的调用方式。让我们来学习如何编写描述函数的类型。
1. 函数类型表达式
- 可以使用类似于C#中Lambda表达式的箭头符号:
typescript
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
function printToConsole(s: string) {
console.log(s);
}
greeter(printToConsole);
- 为函数类型定义别名:
typescript
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
// ...
}
2. 调用签名(Call Signatures)
- 调用签名可以为函数定义额外成员:
typescript
type DescribableFunction = {
description: string; //额外成员
(someArg: number): boolean; //函数体
};
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6)); // 成员读取
}
function myFunc(someArg: number) {
return someArg > 3;
}
myFunc.description = "default description"; //成员赋值
doSomething(myFunc);
- 使用
new操作符添加构造函数:
typescript
type SomeConstructor = {
new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
return new ctor("hello");
}
- 以接口形式定义:
typescript
interface CallOrConstruct {
(n?: number): string; // 常规函数
new (s: string): Date; // 构造函数
}
function fn(ctor: CallOrConstruct) {
console.log(ctor(10)); //由于传入参数类型为number,会选择常规函数
console.log(new ctor("10")); //由于传入参数类型为string,会选择构造函数
}
fn(Date);
3. 泛型(Generics)
typescript
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0];
}
- 使用
extends关键字为泛型参数定义约束:
typescript
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}
4. 可缺省参数(Optional Parameters)
- 使用
?操作符 (参数值默认为undefined):
typescript
function f(x?: number) {
return x;
}
f(); // undefined
f(10); // 10
- 显式指定默认值:
typescript
function f(x: number = 10) {
// ...
}
f(); // 10
f(10); ·// 10
- 回调函数中使用可缺省参数的问题:
typescript
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i);
}
}
myForEach([1,2,3], (x) => console.log(x));
5. 函数重载(Overload)
在 TypeScript 中,我们可以通过编写重载签名 来指定一个函数可以以不同的方式调用。唯一提供实现的函数被称为实现签名(Implementation Signature) ,其余为重载签名(Overload Signatures)。
typescript
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); //OK
const d2 = makeDate(5, 5, 5); //OK
const d3 = makeDate(1, 3); //失败
- 只有重载签名可以被调用:
typescript
function fn(x: string): void;
function fn() {
// ...
}
fn(); //调用失败
- 实现签名必须兼容重载签名:
typescript
function fn(x: boolean): void;
function fn(x: string): void;
//输入参数不兼容
function fn(x: boolean) {}
function fn(x: string): string;
function fn(x: number): boolean;
//返回类型不兼容
function fn(x: string | number) {
return "oops";
}
function len(s: string): number;
function len(arr: any[]): number;
//兼容
function len(x: any) {
return x.length;
}
6. 函数中声明this
TypeScript 会通过代码流分析来推断函数中的 this 应该是什么。
typescript
const user = {
id: 123,
admin: false,
becomeAdmin: function () {
this.admin = true;
},
};
- 有很多情况下,你需要对它所代表的对象进行更多控制。
typescript
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
const db = getDB();
const admins = db.filterUsers(function (this: User) {
return this.admin;
});
注 : JavaScript 规范规定不能有名为 this 的参数,因此 TypeScript 利用这个语法空间,允许你在函数体中声明 this 的类型。
- 不能使用箭头函数 ,因为它会此时会捕捉全局的上下文作为
this。
typescript
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
const db = getDB();
const admins = db.filterUsers(() => this.admin); // 错误
7. 特殊的类型的使用
void:会根据函数是否返回有效对象自行推断:
typescript
function noop() {
return;
}
注 : 在 JavaScript 中,不返回任何值的函数会隐式返回 undefined。然而,在 TypeScript 中,void 和 undefined 并非同一概念;
object指的是任何非基本类型 (字符串、数字、bigint、布尔值、符号、null 或 undefined)的值。这与空对象类型{}不同,也与全局类型 Object 不同;
注 : 在 JavaScript 中,函数值是对象,内在的含义是它们拥有属性,原型链中包含 Object.prototype,是 Object 的实例,可以调用 Object.keys 等方法。因此,在 TypeScript 中,函数类型也被视为对象。
unknown:未知类型表示任意值。这类似于any类型,但更安全 ,因为对未知值进行任何操作都是不合法:
typescript
function f1(a: any) {
a.b(); // OK
}
function f2(a: unknown) {
a.b(); // Fail: 'a' is of type 'unknown'.
}
在描述函数类型 时非常有用,可以描述接受任何值的函数,而无需在函数体中包含任何值。
typescript
function safeParse(s: string): unknown {
return JSON.parse(s);
}
// Need to be careful with 'obj'!
const obj = safeParse(someRandomString);
never: 表示永远不会被观察到的值。在返回类型中,这意味着函数会抛出异常或终止程序执行。
typescript
function fail(msg: string): never {
throw new Error(msg);
}
Function: 描述了 JavaScript 中所有函数值都具有的属性,例如bind、call和apply等。它还有一个特殊属性,即Function类型的值始终可以被调用 ;这些调用会返回任意值:
typescript
function doSomething(f: Function) {
return f(1, 2, 3);
}
8. 剩余参数(Rest Parameters and Arguments)
- 除了使用可选参数或重载来创建可以接受各种固定参数数量的函数之外,我们还可以使用剩余参数来定义接受无限数量参数 的函数。类似于C#的
params关键字。
typescript
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x);
}
// 'a' gets value [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);
-
参数的类型注解隐式为
any[]而不是any,并且任何给定的类型注解都必须是Array<T>或T[]的形式,或者是一个元组类型。 -
我们可以使用扩展语法从可迭代对象(例如数组)中提供可变数量的参数。相当于Python的拆包。
typescript
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);
- TypeScript 并不假定数组是不可变的,所以不能通过上述方式作为具有固定参数函数的输入:
typescript
const args = [8, 5];
const angle = Math.atan2(...args); // 错误
- 解决方案: 通过
as const将其固定下来:
typescript
const args = [8, 5] as const;
const angle = Math.atan2(...args); // 正确
9. 参数解构(Parameter Destructuring)
你可以使用参数解构来方便地将作为参数提供的对象解包到函数体内的一个或多个局部变量中。
typescript
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
console.log(a + b + c);
}
10. 可赋值型(Assignability)
- 上下文类型设置(返回类型为
void)并不会强制函数不返回任何值:
typescript
type voidFunc = () => void;
// OK
const f1: voidFunc = () => {
return true;
};
// OK
const f2: voidFunc = () => true;
// OK
const f3: voidFunc = function () {
return true;
};
- 当
void函数的返回值被赋给另一个变量时,它将保持void类型:
typescript
const v1 = f1();
const v2 = f2();
const v3 = f3();
- 这种行为的存在是因为即使函数返回了一个值,但是接收方可以丢弃,所以如下的代码是合法的:
typescript
const src = [1, 2, 3];
const dst = [0];
src.forEach((el) => dst.push(el));
- 当字面函数 定义具有
void返回类型时,该函数不能返回任何内容:
typescript
function f2(): void {
return true; // 错误
}
const f3 = function (): void {
return true; // 错误
};