函数是任何应用的基本构建块,无论它们是本地函数、从另一个模块导入的函数,还是类中的方法。它们也是值,就像其他值一样,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:
tsTry
functiongreeter (fn : (a : string) => void) {fn ("Hello, World");}functionprintToConsole (s : string) {console .log (s );}greeter (printToConsole );
语法 (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 namedstring
of typeany
“!
当然,我们可以使用类型别名来命名函数类型:
¥Of course, we can use a type alias to name a function type:
tsTry
typeGreetFunction = (a : string) => void;functiongreeter (fn :GreetFunction ) {// ...}
调用签名
¥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:
tsTry
typeDescribableFunction = {description : string;(someArg : number): boolean;};functiondoSomething (fn :DescribableFunction ) {console .log (fn .description + " returned " +fn (6));}functionmyFunc (someArg : number) {returnsomeArg > 3;}myFunc .description = "default description";doSomething (myFunc );
请注意,与函数类型表达式相比,语法略有不同 - 在参数列表和返回类型之间使用 :
而不是 =>
。
¥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:
tsTry
typeSomeConstructor = {new (s : string):SomeObject ;};functionfn (ctor :SomeConstructor ) {return newctor ("hello");}
一些对象,比如 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:
tsTry
interfaceCallOrConstruct {(n ?: number): string;new (s : string):Date ;}
泛型函数
¥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:
tsTry
functionfirstElement (arr : any[]) {returnarr [0];}
这个函数完成了它的工作,但不幸的是返回类型为 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:
tsTry
functionfirstElement <Type >(arr :Type []):Type | undefined {returnarr [0];}
通过向该函数添加类型参数 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:
tsTry
// s is of type 'string'consts =firstElement (["a", "b", "c"]);// n is of type 'number'constn =firstElement ([1, 2, 3]);// u is of type undefinedconstu =firstElement ([]);
推断
¥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:
tsTry
functionmap <Input ,Output >(arr :Input [],func : (arg :Input ) =>Output ):Output [] {returnarr .map (func );}// Parameter 'n' is of type 'string'// 'parsed' is of type 'number[]'constparsed =map (["1", "2", "3"], (n ) =>parseInt (n ));
请注意,在此示例中,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:
tsTry
functionlongest <Type extends {length : number }>(a :Type ,b :Type ) {if (a .length >=b .length ) {returna ;} else {returnb ;}}// longerArray is of type 'number[]'constlongerArray =longest ([1, 2], [1, 2, 3]);// longerString is of type 'alice' | 'bob'constlongerString =longest ("alice", "bob");// Error! Numbers don't have a 'length' propertyconstArgument 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; }'.notOK =longest (10 , 100);
在这个例子中有一些有趣的事情需要注意。我们允许 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 }
,所以我们可以访问 a
和 b
参数的 .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.
longerArray
和 longerString
的类型是根据参数推断出来的。请记住,泛型就是将两个或多个具有相同类型的值关联起来!
¥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:
tsTry
functionminimumLength <Type extends {length : number }>(obj :Type ,minimum : number):Type {if (obj .length >=minimum ) {returnobj ;} else {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; }'.return {length :minimum };}}
看起来这个函数似乎还可以 - 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:
tsTry
// 'arr' gets value { length: 6 }constarr =minimumLength ([1, 2, 3], 6);// and crashes here because arrays have// a 'slice' method, but not the returned object!console .log (arr .slice (0));
指定类型参数
¥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:
tsTry
functioncombine <Type >(arr1 :Type [],arr2 :Type []):Type [] {returnarr1 .concat (arr2 );}
通常使用不匹配的数组调用此函数会出错:
¥Normally it would be an error to call this function with mismatched arrays:
tsTry
constType 'string' is not assignable to type 'number'.2322Type 'string' is not assignable to type 'number'.arr =combine ([1, 2, 3], ["hello" ]);
但是,如果你打算这样做,你可以手动指定 Type
:
¥If you intended to do this, however, you could manually specify Type
:
tsTry
constarr =combine <string | number>([1, 2, 3], ["hello"]);
编写良好泛型函数的指南
¥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:
tsTry
functionfirstElement1 <Type >(arr :Type []) {returnarr [0];}functionfirstElement2 <Type extends any[]>(arr :Type ) {returnarr [0];}// a: number (good)consta =firstElement1 ([1, 2, 3]);// b: any (bad)constb =firstElement2 ([1, 2, 3]);
乍一看,这些似乎相同,但 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:
tsTry
functionfilter1 <Type >(arr :Type [],func : (arg :Type ) => boolean):Type [] {returnarr .filter (func );}functionfilter2 <Type ,Func extends (arg :Type ) => boolean>(arr :Type [],func :Func ):Type [] {returnarr .filter (func );}
我们创建了一个不关联两个值的类型参数 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:
tsTry
functiongreet <Str extends string>(s :Str ) {console .log ("Hello, " +s );}greet ("world");
我们可以很容易地编写一个更简单的版本:
¥We could just as easily have written a simpler version:
tsTry
functiongreet (s : string) {console .log ("Hello, " +s );}
请记住,类型参数用于关联多个值的类型。如果一个类型参数只在函数签名中使用一次,它就没有任何关系。这包括推断的返回类型;例如,如果 Str
是 greet
的推断返回类型的一部分,它将关联参数和返回类型,因此尽管在书面代码中只出现一次,但它会被使用两次。
¥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 中的函数通常采用可变数量的参数。例如,number
的 toFixed
方法采用可选的位数计数:
¥Functions in JavaScript often take a variable number of arguments.
For example, the toFixed
method of number
takes an optional digit count:
tsTry
functionf (n : number) {console .log (n .toFixed ()); // 0 argumentsconsole .log (n .toFixed (3)); // 1 argument}
我们可以通过使用 ?
将参数标记为可选来在 TypeScript 中对此进行建模:
¥We can model this in TypeScript by marking the parameter as optional with ?
:
tsTry
functionf (x ?: number) {// ...}f (); // OKf (10); // OK
尽管参数被指定为 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:
tsTry
functionf (x = 10) {// ...}
现在在 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:
tsTry
// All OKf ();f (10);f (undefined );
回调中的可选参数
¥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:
tsTry
functionmyForEach (arr : any[],callback : (arg : any,index ?: number) => void) {for (leti = 0;i <arr .length ;i ++) {callback (arr [i ],i );}}
人们在编写 index?
作为可选参数时通常的意图是他们希望这两个调用都是合法的:
¥What people usually intend when writing index?
as an optional parameter is that they want both of these calls to be legal:
tsTry
myForEach ([1, 2, 3], (a ) =>console .log (a ));myForEach ([1, 2, 3], (a ,i ) =>console .log (a ,i ));
这实际上意味着 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:
tsTry
functionmyForEach (arr : any[],callback : (arg : any,index ?: number) => void) {for (leti = 0;i <arr .length ;i ++) {// I don't feel like providing the index todaycallback (arr [i ]);}}
反过来,TypeScript 将强制执行此含义并触发实际上不可能的错误:
¥In turn, TypeScript will enforce this meaning and issue errors that aren’t really possible:
tsTry
myForEach ([1, 2, 3], (a ,i ) => {'i' is possibly 'undefined'.18048'i' is possibly 'undefined'.console .log (. i toFixed ());});
在 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:
tsTry
functionmakeDate (timestamp : number):Date ;functionmakeDate (m : number,d : number,y : number):Date ;functionmakeDate (mOrTimestamp : number,d ?: number,y ?: number):Date {if (d !==undefined &&y !==undefined ) {return newDate (y ,mOrTimestamp ,d );} else {return newDate (mOrTimestamp );}}constd1 =makeDate (12345678);constd2 =makeDate (5, 5, 5);constNo 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.d3 =(1, 3); makeDate
在这个例子中,我们写了两个重载:一个接受一个参数,另一个接受三个参数。前两个签名称为重载签名。
¥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:
tsTry
functionfn (x : string): void;functionfn () {// ...}// Expected to be able to call with zero argumentsExpected 1 arguments, but got 0.2554Expected 1 arguments, but got 0.(); fn
同样,用于编写函数体的签名无法从外部 “看到”。
¥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:
tsTry
functionfn (x : boolean): void;// Argument type isn't rightfunctionThis overload signature is not compatible with its implementation signature.2394This overload signature is not compatible with its implementation signature.( fn x : string): void;functionfn (x : boolean) {}
tsTry
functionfn (x : string): string;// Return type isn't rightfunctionThis overload signature is not compatible with its implementation signature.2394This overload signature is not compatible with its implementation signature.( fn x : number): boolean;functionfn (x : string | number) {return "oops";}
编写好的重载
¥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:
tsTry
functionlen (s : string): number;functionlen (arr : any[]): number;functionlen (x : any) {returnx .length ;}
这个函数很好;我们可以用字符串或数组调用它。但是,我们不能使用可能是字符串或数组的值来调用它,因为 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:
tsTry
len (""); // OKlen ([0]); // OKNo 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[]'.len (Math .random () > 0.5 ? "hello" : [0]);
因为两个重载具有相同的参数计数和相同的返回类型,我们可以改为编写函数的非重载版本:
¥Because both overloads have the same argument count and same return type, we can instead write a non-overloaded version of the function:
tsTry
functionlen (x : any[] | string) {returnx .length ;}
这好多了!调用者可以使用任何一种值来调用它,作为额外的好处,我们不必找出正确的实现签名。
¥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:
tsTry
constuser = {id : 123,admin : false,becomeAdmin : function () {this.admin = true;},};
TypeScript 理解函数 user.becomeAdmin
有一个对应的 this
,它是外部对象 user
。this
,呵呵,很多情况下就够用了,但是很多情况下,你需要更多的控制 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.
tsTry
interfaceDB {filterUsers (filter : (this :User ) => boolean):User [];}constdb =getDB ();constadmins =db .filterUsers (function (this :User ) {return this.admin ;});
这种模式在回调风格的 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:
tsTry
interfaceDB {filterUsers (filter : (this :User ) => boolean):User [];}constdb =getDB ();constThe containing arrow function captures the global value of 'this'.admins =db .filterUsers (() =>this .); admin
Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.7041
7017The containing arrow function captures the global value of 'this'.
Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.
其他需要了解的类型
¥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:
tsTry
// The inferred return type is voidfunctionnoop () {return;}
在 JavaScript 中,不返回任何值的函数将隐式返回值 undefined
。但是,void
和 undefined
在 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.
void
与undefined
不同。¥
void
is not the same asundefined
.
object
特殊类型 object
指的是任何非基础值(string
、number
、bigint
、boolean
、symbol
、null
或 undefined
)。这与空对象类型 { }
不同,也与全局类型 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 notObject
. Always useobject
!
请注意,在 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 object
s 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:
tsTry
functionf1 (a : any) {a .b (); // OK}functionf2 (a : unknown) {'a' is of type 'unknown'.18046'a' is of type 'unknown'.. a b ();}
这在描述函数类型时很有用,因为你可以描述接受任何值而不在函数体中包含 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:
tsTry
functionsafeParse (s : string): unknown {returnJSON .parse (s );}// Need to be careful with 'obj'!constobj =safeParse (someRandomString );
never
有些函数从不返回值:
¥Some functions never return a value:
tsTry
functionfail (msg : string): never {throw newError (msg );}
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.
tsTry
functionfn (x : string | number) {if (typeofx === "string") {// do something} else if (typeofx === "number") {// do something else} else {x ; // has type 'never'!}}
Function
全局类型 Function
描述了 bind
、call
、apply
等属性,以及 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
:
tsTry
functiondoSomething (f :Function ) {returnf (1, 2, 3);}
这是一个无类型的函数调用,通常最好避免,因为不安全的 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:
tsTry
functionmultiply (n : number, ...m : number[]) {returnm .map ((x ) =>n *x );}// 'a' gets value [10, 20, 30, 40]consta =multiply (10, 1, 2, 3, 4);
在 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:
tsTry
constarr1 = [1, 2, 3];constarr2 = [4, 5, 6];arr1 .push (...arr2 );
请注意,通常,TypeScript 并不假定数组是不可变的。这可能会导致一些令人惊讶的行为:
¥Note that in general, TypeScript does not assume that arrays are immutable. This can lead to some surprising behavior:
tsTry
// Inferred type is number[] -- "an array with zero or more numbers",// not specifically two numbersconstargs = [8, 5];constA 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.angle =Math .atan2 (...args );
这种情况的最佳解决方案取决于你的代码,但通常 const
上下文是最直接的解决方案:
¥The best fix for this situation depends a bit on your code, but in general a const
context is the most straightforward solution:
tsTry
// Inferred as 2-length tupleconstargs = [8, 5] asconst ;// OKconstangle =Math .atan2 (...args );
针对较旧的运行时,使用剩余参数可能需要打开 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:
tsTry
functionsum ({a ,b ,c }: {a : number;b : number;c : number }) {console .log (a +b +c );}
这看起来有点冗长,但你也可以在此处使用命名类型:
¥This can look a bit verbose, but you can use a named type here as well:
tsTry
// Same as prior exampletypeABC = {a : number;b : number;c : number };functionsum ({a ,b ,c }:ABC ) {console .log (a +b +c );}
函数的可赋值性
¥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:
tsTry
typevoidFunc = () => void;constf1 :voidFunc = () => {return true;};constf2 :voidFunc = () => true;constf3 :voidFunc = function () {return true;};
并且当其中一个函数的返回值被赋值给另一个变量时,它会保留 void
的类型:
¥And when the return value of one of these functions is assigned to another variable, it will retain the type of void
:
tsTry
constv1 =f1 ();constv2 =f2 ();constv3 =f3 ();
这种行为的存在使得即使 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
.
tsTry
constsrc = [1, 2, 3];constdst = [0];src .forEach ((el ) =>dst .push (el ));
还有另一种特殊情况需要注意,当字面量函数定义具有 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.
tsTry
functionf2 (): void {// @ts-expect-errorreturn true;}constf3 = function (): void {// @ts-expect-errorreturn true;};
有关 void
的更多信息,请参阅这些其他文档条目:
¥For more on void
please refer to these other documentation entries: