更多关于函数

函数是任何应用的基本构建块,无论它们是本地函数、从另一个模块导入的函数,还是类中的方法。它们也是值,就像其他值一样,TypeScript 有很多方法来描述如何调用函数。让我们学习如何编写描述函数的类型。

¥Functions are the basic building block of any application, whether they’re local functions, imported from another module, or methods on a class. They’re also values, and just like other values, TypeScript has many ways to describe how functions can be called. Let’s learn about how to write types that describe functions.

函数类型表达式

¥Function Type Expressions

描述函数的最简单方法是使用函数类型表达式。这些类型在语法上类似于箭头函数:

¥The simplest way to describe a function is with a function type expression. These types are syntactically similar to arrow functions:

ts
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
 
function printToConsole(s: string) {
console.log(s);
}
 
greeter(printToConsole);
Try

语法 (a: string) => void 的意思是“一个带有一个参数、名为 a、类型为 string、没有返回值的函数”。就像函数声明一样,如果未指定参数类型,则隐式为 any

¥The syntax (a: string) => void means “a function with one parameter, named a, of type string, that doesn’t have a return value”. Just like with function declarations, if a parameter type isn’t specified, it’s implicitly any.

请注意,参数名称是必需的。函数类型 (string) => void 的意思是“一个带有名为 string、类型为 any 的参数的函数”!

¥Note that the parameter name is required. The function type (string) => void means “a function with a parameter named string of type any“!

当然,我们可以使用类型别名来命名函数类型:

¥Of course, we can use a type alias to name a function type:

ts
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
// ...
}
Try

调用签名

¥Call Signatures

在 JavaScript 中,函数除了可调用之外还可以具有属性。但是,函数类型表达式语法不允许声明属性。如果我们想用属性描述可调用的东西,我们可以在对象类型中编写调用签名:

¥In JavaScript, functions can have properties in addition to being callable. However, the function type expression syntax doesn’t allow for declaring properties. If we want to describe something callable with properties, we can write a call signature in an object type:

ts
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);
Try

请注意,与函数类型表达式相比,语法略有不同 - 在参数列表和返回类型之间使用 : 而不是 =>

¥Note that the syntax is slightly different compared to a function type expression - use : between the parameter list and the return type rather than =>.

构造签名

¥Construct Signatures

JavaScript 函数也可以使用 new 运算符调用。TypeScript 将它们称为构造函数,因为它们通常会创建一个新对象。你可以通过在调用签名前添加 new 关键字来编写构造签名:

¥JavaScript functions can also be invoked with the new operator. TypeScript refers to these as constructors because they usually create a new object. You can write a construct signature by adding the new keyword in front of a call signature:

ts
type SomeConstructor = {
new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
return new ctor("hello");
}
Try

一些对象,比如 JavaScript 的 Date 对象,可以在有或没有 new 的情况下调用。你可以任意组合相同类型的调用和构造签名:

¥Some objects, like JavaScript’s Date object, can be called with or without new. You can combine call and construct signatures in the same type arbitrarily:

ts
interface CallOrConstruct {
(n?: number): string;
new (s: string): Date;
}
 
function fn(ctor: CallOrConstruct) {
// Passing an argument of type `number` to `ctor` matches it against
// the first definition in the `CallOrConstruct` interface.
console.log(ctor(10));
(parameter) ctor: CallOrConstruct (n?: number) => string
 
// Similarly, passing an argument of type `string` to `ctor` matches it
// against the second definition in the `CallOrConstruct` interface.
console.log(new ctor("10"));
(parameter) ctor: CallOrConstruct new (s: string) => Date
}
 
fn(Date);
Try

泛型函数

¥Generic Functions

通常会编写一个函数,其中输入的类型与输出的类型相关,或者两个输入的类型以某种方式相关。让我们考虑一个返回数组第一个元素的函数:

¥It’s common to write a function where the types of the input relate to the type of the output, or where the types of two inputs are related in some way. Let’s consider for a moment a function that returns the first element of an array:

ts
function firstElement(arr: any[]) {
return arr[0];
}
Try

这个函数完成了它的工作,但不幸的是返回类型为 any。如果函数返回数组元素的类型会更好。

¥This function does its job, but unfortunately has the return type any. It’d be better if the function returned the type of the array element.

在 TypeScript 中,当我们想要描述两个值之间的对应关系时,会使用泛型。我们通过在函数签名中声明一个类型参数来做到这一点:

¥In TypeScript, generics are used when we want to describe a correspondence between two values. We do this by declaring a type parameter in the function signature:

ts
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0];
}
Try

通过向该函数添加类型参数 Type 并在两个地方使用它,我们在函数的输入(数组)和输出(返回值)之间创建了一个链接。现在当我们调用它时,会出现一个更具体的类型:

¥By adding a type parameter Type to this function and using it in two places, we’ve created a link between the input of the function (the array) and the output (the return value). Now when we call it, a more specific type comes out:

ts
// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);
// u is of type undefined
const u = firstElement([]);
Try

推断

¥Inference

请注意,我们不必在此示例中指定 Type。类型被推断 - 自动选择 - 通过 TypeScript。

¥Note that we didn’t have to specify Type in this sample. The type was inferred - chosen automatically - by TypeScript.

我们也可以使用多个类型参数。例如,map 的独立版本如下所示:

¥We can use multiple type parameters as well. For example, a standalone version of map would look like this:

ts
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));
Try

请注意,在此示例中,TypeScript 可以根据函数表达式 (number) 的返回值推断 Input 类型参数的类型(从给定的 string 数组)以及 Output 类型参数。

¥Note that in this example, TypeScript could infer both the type of the Input type parameter (from the given string array), as well as the Output type parameter based on the return value of the function expression (number).

约束条件

¥Constraints

我们编写了一些泛型函数,可以处理任何类型的值。有时我们想关联两个值,但只能对某个值的子集进行操作。在这种情况下,我们可以使用约束来限制类型参数可以接受的类型种类。

¥We’ve written some generic functions that can work on any kind of value. Sometimes we want to relate two values, but can only operate on a certain subset of values. In this case, we can use a constraint to limit the kinds of types that a type parameter can accept.

让我们编写一个返回两个值中较长者的函数。为此,我们需要一个 length 属性,它是一个数字。我们通过编写 extends 子句将类型参数限制为该类型:

¥Let’s write a function that returns the longer of two values. To do this, we need a length property that’s a number. We constrain the type parameter to that type by writing an extends clause:

ts
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}
 
// longerArray is of type 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString is of type 'alice' | 'bob'
const longerString = longest("alice", "bob");
// Error! Numbers don't have a 'length' property
const notOK = longest(10, 100);
Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.2345Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
Try

在这个例子中有一些有趣的事情需要注意。我们允许 TypeScript 推断 longest 的返回类型。返回类型推断也适用于泛型函数。

¥There are a few interesting things to note in this example. We allowed TypeScript to infer the return type of longest. Return type inference also works on generic functions.

因为我们将 Type 限制为 { length: number },所以我们可以访问 ab 参数的 .length 属性。如果没有类型约束,我们将无法访问这些属性,因为这些值可能是没有长度属性的其他类型。

¥Because we constrained Type to { length: number }, we were allowed to access the .length property of the a and b parameters. Without the type constraint, we wouldn’t be able to access those properties because the values might have been some other type without a length property.

longerArraylongerString 的类型是根据参数推断出来的。请记住,泛型就是将两个或多个具有相同类型的值关联起来!

¥The types of longerArray and longerString were inferred based on the arguments. Remember, generics are all about relating two or more values with the same type!

最后,正如我们所愿,对 longest(10, 100) 的调用被拒绝,因为 number 类型没有 .length 属性。

¥Finally, just as we’d like, the call to longest(10, 100) is rejected because the number type doesn’t have a .length property.

使用约束值

¥Working with Constrained Values

这是使用泛型约束时的一个常见错误:

¥Here’s a common error when working with generic constraints:

ts
function minimumLength<Type extends { length: number }>(
obj: Type,
minimum: number
): Type {
if (obj.length >= minimum) {
return obj;
} else {
return { length: minimum };
Type '{ length: number; }' is not assignable to type 'Type'. '{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.2322Type '{ length: number; }' is not assignable to type 'Type'. '{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.
}
}
Try

看起来这个函数似乎还可以 - Type 被约束为 { length: number },并且该函数返回 Type 或与该约束匹配的值。问题是该函数 promise 返回与传入相同类型的对象,而不仅仅是与约束匹配的某个对象。如果这段代码是合法的,你可以编写绝对行不通的代码:

¥It might look like this function is OK - Type is constrained to { length: number }, and the function either returns Type or a value matching that constraint. The problem is that the function promises to return the same kind of object as was passed in, not just some object matching the constraint. If this code were legal, you could write code that definitely wouldn’t work:

ts
// 'arr' gets value { length: 6 }
const arr = minimumLength([1, 2, 3], 6);
// and crashes here because arrays have
// a 'slice' method, but not the returned object!
console.log(arr.slice(0));
Try

指定类型参数

¥Specifying Type Arguments

TypeScript 通常可以在泛型调用中推断出预期的类型参数,但并非总是如此。例如,假设你编写了一个函数来组合两个数组:

¥TypeScript can usually infer the intended type arguments in a generic call, but not always. For example, let’s say you wrote a function to combine two arrays:

ts
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
Try

通常使用不匹配的数组调用此函数会出错:

¥Normally it would be an error to call this function with mismatched arrays:

ts
const arr = combine([1, 2, 3], ["hello"]);
Type 'string' is not assignable to type 'number'.2322Type 'string' is not assignable to type 'number'.
Try

但是,如果你打算这样做,你可以手动指定 Type

¥If you intended to do this, however, you could manually specify Type:

ts
const arr = combine<string | number>([1, 2, 3], ["hello"]);
Try

编写良好泛型函数的指南

¥Guidelines for Writing Good Generic Functions

编写泛型函数很有趣,而且很容易被类型参数迷住。拥有太多类型参数或在不需要它们的地方使用约束会使推断不太成功,从而使函数的调用者感到沮丧。

¥Writing generic functions is fun, and it can be easy to get carried away with type parameters. Having too many type parameters or using constraints where they aren’t needed can make inference less successful, frustrating callers of your function.

下推类型参数

¥Push Type Parameters Down

以下是编写看起来相似的函数的两种方法:

¥Here are two ways of writing a function that appear similar:

ts
function firstElement1<Type>(arr: Type[]) {
return arr[0];
}
 
function firstElement2<Type extends any[]>(arr: Type) {
return arr[0];
}
 
// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);
Try

乍一看,这些似乎相同,但 firstElement1 是编写此函数的更好方法。它推断的返回类型是 Type,但 firstElement2 的推断返回类型是 any,因为 TypeScript 必须使用约束类型来解析 arr[0] 表达式,而不是 “等待” 在调用期间解析元素。

¥These might seem identical at first glance, but firstElement1 is a much better way to write this function. Its inferred return type is Type, but firstElement2’s inferred return type is any because TypeScript has to resolve the arr[0] expression using the constraint type, rather than “waiting” to resolve the element during a call.

规则:如果可能,使用类型参数本身而不是约束它

¥Rule: When possible, use the type parameter itself rather than constraining it

使用更少的类型参数

¥Use Fewer Type Parameters

这是另一对类似的函数:

¥Here’s another pair of similar functions:

ts
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
return arr.filter(func);
}
 
function filter2<Type, Func extends (arg: Type) => boolean>(
arr: Type[],
func: Func
): Type[] {
return arr.filter(func);
}
Try

我们创建了一个不关联两个值的类型参数 Func。这总是一个危险信号,因为这意味着想要指定类型参数的调用者必须无缘无故地手动指定额外的类型参数。Func 没有做任何事情,只是让函数更难阅读和推断!

¥We’ve created a type parameter Func that doesn’t relate two values. That’s always a red flag, because it means callers wanting to specify type arguments have to manually specify an extra type argument for no reason. Func doesn’t do anything but make the function harder to read and reason about!

规则:始终使用尽可能少的类型参数

¥Rule: Always use as few type parameters as possible

类型参数应该出现两次

¥Type Parameters Should Appear Twice

有时我们忘记了函数可能不需要是泛型的:

¥Sometimes we forget that a function might not need to be generic:

ts
function greet<Str extends string>(s: Str) {
console.log("Hello, " + s);
}
 
greet("world");
Try

我们可以很容易地编写一个更简单的版本:

¥We could just as easily have written a simpler version:

ts
function greet(s: string) {
console.log("Hello, " + s);
}
Try

请记住,类型参数用于关联多个值的类型。如果一个类型参数只在函数签名中使用一次,它就没有任何关系。这包括推断的返回类型;例如,如果 Strgreet 的推断返回类型的一部分,它将关联参数和返回类型,因此尽管在书面代码中只出现一次,但它会被使用两次。

¥Remember, type parameters are for relating the types of multiple values. If a type parameter is only used once in the function signature, it’s not relating anything. This includes the inferred return type; for example, if Str was part of the inferred return type of greet, it would be relating the argument and return types, so would be used twice despite appearing only once in the written code.

规则:如果一个类型参数只出现在一个位置,强烈重新考虑是否真的需要它

¥Rule: If a type parameter only appears in one location, strongly reconsider if you actually need it

可选参数

¥Optional Parameters

JavaScript 中的函数通常采用可变数量的参数。例如,numbertoFixed 方法采用可选的位数计数:

¥Functions in JavaScript often take a variable number of arguments. For example, the toFixed method of number takes an optional digit count:

ts
function f(n: number) {
console.log(n.toFixed()); // 0 arguments
console.log(n.toFixed(3)); // 1 argument
}
Try

我们可以通过使用 ? 将参数标记为可选来在 TypeScript 中对此进行建模:

¥We can model this in TypeScript by marking the parameter as optional with ?:

ts
function f(x?: number) {
// ...
}
f(); // OK
f(10); // OK
Try

尽管参数被指定为 number 类型,但 x 参数实际上将具有 number | undefined 类型,因为 JavaScript 中未指定的参数获取值 undefined

¥Although the parameter is specified as type number, the x parameter will actually have the type number | undefined because unspecified parameters in JavaScript get the value undefined.

你还可以提供参数默认值:

¥You can also provide a parameter default:

ts
function f(x = 10) {
// ...
}
Try

现在在 f 的主体中,x 将具有 number 类型,因为任何 undefined 参数都将被 10 替换。请注意,当参数是可选的时,调用者始终可以传递 undefined,因为这只是模拟 “missing” 参数:

¥Now in the body of f, x will have type number because any undefined argument will be replaced with 10. Note that when a parameter is optional, callers can always pass undefined, as this simply simulates a “missing” argument:

ts
// All OK
f();
f(10);
f(undefined);
Try

回调中的可选参数

¥Optional Parameters in Callbacks

一旦你了解了可选参数和函数类型表达式,在编写调用回调的函数时很容易犯以下错误:

¥Once you’ve learned about optional parameters and function type expressions, it’s very easy to make the following mistakes when writing functions that invoke callbacks:

ts
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i);
}
}
Try

人们在编写 index? 作为可选参数时通常的意图是他们希望这两个调用都是合法的:

¥What people usually intend when writing index? as an optional parameter is that they want both of these calls to be legal:

ts
myForEach([1, 2, 3], (a) => console.log(a));
myForEach([1, 2, 3], (a, i) => console.log(a, i));
Try

这实际上意味着 callback 可能会被一个参数调用。换句话说,函数定义表明实现可能如下所示:

¥What this actually means is that callback might get invoked with one argument. In other words, the function definition says that the implementation might look like this:

ts
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]);
}
}
Try

反过来,TypeScript 将强制执行此含义并触发实际上不可能的错误:

¥In turn, TypeScript will enforce this meaning and issue errors that aren’t really possible:

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

在 JavaScript 中,如果你调用一个参数多于参数的函数,多余的参数将被忽略。TypeScript 的行为方式相同。具有较少参数(相同类型)的函数总是可以代替具有更多参数的函数。

¥In JavaScript, if you call a function with more arguments than there are parameters, the extra arguments are simply ignored. TypeScript behaves the same way. Functions with fewer parameters (of the same types) can always take the place of functions with more parameters.

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

¥Rule: When writing a function type for a callback, never write an optional parameter unless you intend to call the function without passing that argument

函数重载

¥Function Overloads

可以以各种参数计数和类型调用一些 JavaScript 函数。例如,你可以编写一个函数来生成一个 Date,它采用时间戳(一个参数)或月/日/年规范(三个参数)。

¥Some JavaScript functions can be called in a variety of argument counts and types. For example, you might write a function to produce a Date that takes either a timestamp (one argument) or a month/day/year specification (three arguments).

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

¥In TypeScript, we can specify a function that can be called in different ways by writing overload signatures. To do this, write some number of function signatures (usually two or more), followed by the body of the function:

ts
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);
No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.2575No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
Try

在这个例子中,我们写了两个重载:一个接受一个参数,另一个接受三个参数。前两个签名称为重载签名。

¥In this example, we wrote two overloads: one accepting one argument, and another accepting three arguments. These first two signatures are called the overload signatures.

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

¥Then, we wrote a function implementation with a compatible signature. Functions have an implementation signature, but this signature can’t be called directly. Even though we wrote a function with two optional parameters after the required one, it can’t be called with two parameters!

重载签名和实现签名

¥Overload Signatures and the Implementation Signature

这是一个常见的混淆来源。很多时候人们会写这样的代码,却不明白为什么会出现错误:

¥This is a common source of confusion. Often people will write code like this and not understand why there is an error:

ts
function fn(x: string): void;
function fn() {
// ...
}
// Expected to be able to call with zero arguments
fn();
Expected 1 arguments, but got 0.2554Expected 1 arguments, but got 0.
Try

同样,用于编写函数体的签名无法从外部 “看到”。

¥Again, the signature used to write the function body can’t be “seen” from the outside.

从外部看不到实现的签名。在编写重载函数时,你应该始终在函数实现之上有两个或多个签名。

¥The signature of the implementation is not visible from the outside. When writing an overloaded function, you should always have two or more signatures above the implementation of the function.

实现签名还必须与重载签名兼容。例如,这些函数有错误,因为实现签名没有以正确的方式匹配重载:

¥The implementation signature must also be compatible with the overload signatures. For example, these functions have errors because the implementation signature doesn’t match the overloads in a correct way:

ts
function fn(x: boolean): void;
// Argument type isn't right
function fn(x: string): void;
This overload signature is not compatible with its implementation signature.2394This overload signature is not compatible with its implementation signature.
function fn(x: boolean) {}
Try
ts
function fn(x: string): string;
// Return type isn't right
function fn(x: number): boolean;
This overload signature is not compatible with its implementation signature.2394This overload signature is not compatible with its implementation signature.
function fn(x: string | number) {
return "oops";
}
Try

编写好的重载

¥Writing Good Overloads

与泛型一样,在使用函数重载时应该遵循一些准则。遵循这些原则将使你的函数更易于调用、更易于理解和更易于实现。

¥Like generics, there are a few guidelines you should follow when using function overloads. Following these principles will make your function easier to call, easier to understand, and easier to implement.

让我们考虑一个返回字符串或数组长度的函数:

¥Let’s consider a function that returns the length of a string or an array:

ts
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
return x.length;
}
Try

这个函数很好;我们可以用字符串或数组调用它。但是,我们不能使用可能是字符串或数组的值来调用它,因为 TypeScript 只能将函数调用解析为单个重载:

¥This function is fine; we can invoke it with strings or arrays. However, we can’t invoke it with a value that might be a string or an array, because TypeScript can only resolve a function call to a single overload:

ts
len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]);
No overload matches this call. Overload 1 of 2, '(s: string): number', gave the following error. Argument of type 'number[] | "hello"' is not assignable to parameter of type 'string'. Type 'number[]' is not assignable to type 'string'. Overload 2 of 2, '(arr: any[]): number', gave the following error. Argument of type 'number[] | "hello"' is not assignable to parameter of type 'any[]'. Type 'string' is not assignable to type 'any[]'.2769No overload matches this call. Overload 1 of 2, '(s: string): number', gave the following error. Argument of type 'number[] | "hello"' is not assignable to parameter of type 'string'. Type 'number[]' is not assignable to type 'string'. Overload 2 of 2, '(arr: any[]): number', gave the following error. Argument of type 'number[] | "hello"' is not assignable to parameter of type 'any[]'. Type 'string' is not assignable to type 'any[]'.
Try

因为两个重载具有相同的参数计数和相同的返回类型,我们可以改为编写函数的非重载版本:

¥Because both overloads have the same argument count and same return type, we can instead write a non-overloaded version of the function:

ts
function len(x: any[] | string) {
return x.length;
}
Try

这好多了!调用者可以使用任何一种值来调用它,作为额外的好处,我们不必找出正确的实现签名。

¥This is much better! Callers can invoke this with either sort of value, and as an added bonus, we don’t have to figure out a correct implementation signature.

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

¥Always prefer parameters with union types instead of overloads when possible

在函数中声明 this

¥Declaring this in a Function

TypeScript 将通过代码流分析推断函数中的 this 应该是什么,例如:

¥TypeScript will infer what the this should be in a function via code flow analysis, for example in the following:

ts
const user = {
id: 123,
 
admin: false,
becomeAdmin: function () {
this.admin = true;
},
};
Try

TypeScript 理解函数 user.becomeAdmin 有一个对应的 this,它是外部对象 userthis,呵呵,很多情况下就够用了,但是很多情况下,你需要更多的控制 this 代表什么对象。JavaScript 规范规定你不能有一个名为 this 的参数,因此 TypeScript 使用该语法空间让你在函数体中声明 this 的类型。

¥TypeScript understands that the function user.becomeAdmin has a corresponding this which is the outer object user. this, heh, can be enough for a lot of cases, but there are a lot of cases where you need more control over what object this represents. The JavaScript specification states that you cannot have a parameter called this, and so TypeScript uses that syntax space to let you declare the type for this in the function body.

ts
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(function (this: User) {
return this.admin;
});
Try

这种模式在回调风格的 API 中很常见,其中另一个对象通常控制何时调用你的函数。请注意,你需要使用 function 而不是箭头函数来获得此行为:

¥This pattern is common with callback-style APIs, where another object typically controls when your function is called. Note that you need to use function and not arrow functions to get this behavior:

ts
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(() => this.admin);
The containing arrow function captures the global value of 'this'.
Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.
7041
7017
The containing arrow function captures the global value of 'this'.
Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.
Try

其他需要了解的类型

¥Other Types to Know About

在使用函数类型时,你需要识别一些经常出现的其他类型。像所有类型一样,你可以在任何地方使用它们,但这些在函数的上下文中尤其相关。

¥There are some additional types you’ll want to recognize that appear often when working with function types. Like all types, you can use them everywhere, but these are especially relevant in the context of functions.

void

void 表示不返回值的函数的返回值。只要函数没有任何 return 语句,或者没有从这些返回语句返回任何显式值,它就是推断类型:

¥void represents the return value of functions which don’t return a value. It’s the inferred type any time a function doesn’t have any return statements, or doesn’t return any explicit value from those return statements:

ts
// The inferred return type is void
function noop() {
return;
}
Try

在 JavaScript 中,不返回任何值的函数将隐式返回值 undefined。但是,voidundefined 在 TypeScript 中不是一回事。本章末尾有更多详细信息。

¥In JavaScript, a function that doesn’t return any value will implicitly return the value undefined. However, void and undefined are not the same thing in TypeScript. There are further details at the end of this chapter.

voidundefined 不同。

¥void is not the same as undefined.

object

特殊类型 object 指的是任何非基础值(stringnumberbigintbooleansymbolnullundefined)。这与空对象类型 { } 不同,也与全局类型 Object 不同。你很可能永远不会使用 Object

¥The special type object refers to any value that isn’t a primitive (string, number, bigint, boolean, symbol, null, or undefined). This is different from the empty object type { }, and also different from the global type Object. It’s very likely you will never use Object.

object 不是 Object。始终使用 object

¥object is not Object. Always use object!

请注意,在 JavaScript 中,函数值是对象:它们有属性,在它们的原型链中有 Object.prototype,是 instanceof Object,你可以在它们上调用 Object.keys,等等。因此,函数类型在 TypeScript 中被视为 object

¥Note that in JavaScript, function values are objects: They have properties, have Object.prototype in their prototype chain, are instanceof Object, you can call Object.keys on them, and so on. For this reason, function types are considered to be objects in TypeScript.

unknown

unknown 类型代表任何值。这类似于 any 类型,但更安全,因为使用 unknown 值做任何事情都是不合法的:

¥The unknown type represents any value. This is similar to the any type, but is safer because it’s not legal to do anything with an unknown value:

ts
function f1(a: any) {
a.b(); // OK
}
function f2(a: unknown) {
a.b();
'a' is of type 'unknown'.18046'a' is of type 'unknown'.
}
Try

这在描述函数类型时很有用,因为你可以描述接受任何值而不在函数体中包含 any 值的函数。

¥This is useful when describing function types because you can describe functions that accept any value without having any values in your function body.

相反,你可以描述一个返回未知类型值的函数:

¥Conversely, you can describe a function that returns a value of unknown type:

ts
function safeParse(s: string): unknown {
return JSON.parse(s);
}
 
// Need to be careful with 'obj'!
const obj = safeParse(someRandomString);
Try

never

有些函数从不返回值:

¥Some functions never return a value:

ts
function fail(msg: string): never {
throw new Error(msg);
}
Try

never 类型表示从未观察到的值。在返回类型中,这意味着函数抛出异常或终止程序的执行。

¥The never type represents values which are never observed. In a return type, this means that the function throws an exception or terminates execution of the program.

当 TypeScript 确定联合中没有任何内容时,never 也会出现。

¥never also appears when TypeScript determines there’s nothing left in a union.

ts
function fn(x: string | number) {
if (typeof x === "string") {
// do something
} else if (typeof x === "number") {
// do something else
} else {
x; // has type 'never'!
}
}
Try

Function

全局类型 Function 描述了 bindcallapply 等属性,以及 JavaScript 中所有函数值上的其他属性。它还具有 Function 类型的值始终可以被调用的特殊属性;这些调用返回 any

¥The global type Function describes properties like bind, call, apply, and others present on all function values in JavaScript. It also has the special property that values of type Function can always be called; these calls return any:

ts
function doSomething(f: Function) {
return f(1, 2, 3);
}
Try

这是一个无类型的函数调用,通常最好避免,因为不安全的 any 返回类型。

¥This is an untyped function call and is generally best avoided because of the unsafe any return type.

如果你需要接受任意函数但不打算调用它,则类型 () => void 通常更安全。

¥If you need to accept an arbitrary function but don’t intend to call it, the type () => void is generally safer.

剩余形参和实参

¥Rest Parameters and Arguments

背景阅读:
剩余形参
展开语法

剩余形参

¥Rest Parameters

除了使用可选参数或重载来制作可以接受各种固定参数计数的函数之外,我们还可以使用剩余参数定义接受无限数量参数的函数。

¥In addition to using optional parameters or overloads to make functions that can accept a variety of fixed argument counts, we can also define functions that take an unbounded number of arguments using rest parameters.

剩余参数出现在所有其他参数之后,并使用 ... 语法:

¥A rest parameter appears after all other parameters, and uses the ... syntax:

ts
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);
Try

在 TypeScript 中,这些参数上的类型注释隐式为 any[] 而不是 any,并且给出的任何类型注释必须采用 Array<T>T[] 形式,或者元组类型(稍后我们将了解)。

¥In TypeScript, the type annotation on these parameters is implicitly any[] instead of any, and any type annotation given must be of the form Array<T> or T[], or a tuple type (which we’ll learn about later).

剩余实参

¥Rest Arguments

相反,我们可以使用扩展语法从可迭代对象(例如数组)中提供可变数量的参数。例如,数组的 push 方法接受任意数量的参数:

¥Conversely, we can provide a variable number of arguments from an iterable object (for example, an array) using the spread syntax. For example, the push method of arrays takes any number of arguments:

ts
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);
Try

请注意,通常,TypeScript 并不假定数组是不可变的。这可能会导致一些令人惊讶的行为:

¥Note that in general, TypeScript does not assume that arrays are immutable. This can lead to some surprising behavior:

ts
// Inferred type is number[] -- "an array with zero or more numbers",
// not specifically two numbers
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.2556A spread argument must either have a tuple type or be passed to a rest parameter.
Try

这种情况的最佳解决方案取决于你的代码,但通常 const 上下文是最直接的解决方案:

¥The best fix for this situation depends a bit on your code, but in general a const context is the most straightforward solution:

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

针对较旧的运行时,使用剩余参数可能需要打开 downlevelIteration

¥Using rest arguments may require turning on downlevelIteration when targeting older runtimes.

参数解构

¥Parameter Destructuring

背景阅读:
解构赋值

你可以使用参数解构来方便地将作为参数提供的对象解包到函数体中的一个或多个局部变量中。在 JavaScript 中,它看起来像这样:

¥You can use parameter destructuring to conveniently unpack objects provided as an argument into one or more local variables in the function body. In JavaScript, it looks like this:

js
function sum({ a, b, c }) {
console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 });

对象的类型注释遵循解构语法:

¥The type annotation for the object goes after the destructuring syntax:

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

这看起来有点冗长,但你也可以在此处使用命名类型:

¥This can look a bit verbose, but you can use a named type here as well:

ts
// Same as prior example
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
console.log(a + b + c);
}
Try

函数的可赋值性

¥Assignability of Functions

返回类型 void

¥Return type void

函数的 void 返回类型可能会产生一些不寻常但预期的行为。

¥The void return type for functions can produce some unusual, but expected behavior.

返回类型为 void 的上下文类型不会强制函数不返回某些内容。另一种说法是具有 void 返回类型 (type voidFunc = () => void) 的上下文函数类型,当实现时,可以返回任何其他值,但会被忽略。

¥Contextual typing with a return type of void does not force functions to not return something. Another way to say this is a contextual function type with a void return type (type voidFunc = () => void), when implemented, can return any other value, but it will be ignored.

因此,() => void 类型的以下实现是有效的:

¥Thus, the following implementations of the type () => void are valid:

ts
type voidFunc = () => void;
 
const f1: voidFunc = () => {
return true;
};
 
const f2: voidFunc = () => true;
 
const f3: voidFunc = function () {
return true;
};
Try

并且当其中一个函数的返回值被赋值给另一个变量时,它会保留 void 的类型:

¥And when the return value of one of these functions is assigned to another variable, it will retain the type of void:

ts
const v1 = f1();
 
const v2 = f2();
 
const v3 = f3();
Try

这种行为的存在使得即使 Array.prototype.push 返回一个数字并且 Array.prototype.forEach 方法需要一个返回类型为 void 的函数,以下代码也是有效的。

¥This behavior exists so that the following code is valid even though Array.prototype.push returns a number and the Array.prototype.forEach method expects a function with a return type of void.

ts
const src = [1, 2, 3];
const dst = [0];
 
src.forEach((el) => dst.push(el));
Try

还有另一种特殊情况需要注意,当字面量函数定义具有 void 返回类型时,该函数不得返回任何内容。

¥There is one other special case to be aware of, when a literal function definition has a void return type, that function must not return anything.

ts
function f2(): void {
// @ts-expect-error
return true;
}
 
const f3 = function (): void {
// @ts-expect-error
return true;
};
Try

有关 void 的更多信息,请参阅这些其他文档条目:

¥For more on void please refer to these other documentation entries: