[TS手冊(cè)學(xué)習(xí)] 03_函數(shù)相關(guān)知識(shí)點(diǎn)
TS官方手冊(cè):TypeScript: Handbook - The TypeScript Handbook (typescriptlang.org)
函數(shù)類型表達(dá)式
使用類似于箭頭表達(dá)式的形式來(lái)描述一個(gè)函數(shù)的類型。
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
上述代碼中,fn: (a:string) => void表示變量fn是一個(gè)函數(shù),這個(gè)函數(shù)有一個(gè)參數(shù)a,是string類型,且這個(gè)函數(shù)的返回值類型為void,即沒有返回值。
調(diào)用簽名
在 JS 中,函數(shù)是對(duì)象,除了可以調(diào)用也可以擁有自己的屬性。而使用函數(shù)類型表達(dá)式無(wú)法聲明這一部分屬性的類型。
可以將函數(shù)視為一個(gè)對(duì)象,聲明一個(gè)類型,其中包含多個(gè)屬性的類型聲明,并使用調(diào)用簽名來(lái)描述函數(shù)參數(shù)和返回值的類型,取代原先函數(shù)類型表達(dá)式的寫法。
type DescribableFunction = {
description: string;
(someArg: number): boolean;
};
在這個(gè)例子中,DescribableFunction是一個(gè)函數(shù)類型,description是這個(gè)函數(shù)類型實(shí)例對(duì)象的一個(gè)屬性名,類型為string。而這個(gè)函數(shù)的參數(shù)列表類型聲明為:(someArg: number),返回值類型為boolean。
需要注意,在這種寫法中,參數(shù)列表和返回值類型之間是用:隔開,而函數(shù)類型表達(dá)式是使用=>。
構(gòu)造簽名
搭配構(gòu)造函數(shù)使用,在調(diào)用簽名的語(yǔ)法前面加上new。
type SomeConstructor = {
new (s: string): SomeObject;
};
泛型函數(shù)
如果函數(shù)的參數(shù)類型與返回值的參數(shù)類型存在關(guān)聯(lián),可以使用泛型:
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0];
}
// s是'string'類型
const s = firstElement(["a", "b", "c"]);
// n是'number'類型
const n = firstElement([1, 2, 3]);
// u是undefined類型
const u = firstElement([]);
多類型:
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
return arr.map(func);
}
類型約束:
泛型支持函數(shù)傳入不同的類型,當(dāng)需要約束時(shí),例如要求傳入的參數(shù)類型必須包含一個(gè)某類型的屬性,則可以:
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}
使用<Type extends { x : y}>實(shí)現(xiàn),extends表示繼承于類型{x:y},表示類型Type應(yīng)該包含y類型的屬性x。
需要注意如果函數(shù)返回值類型為Type,那么不能返回類型為{x:y},因?yàn)門ype包含{x:y},而可能存在比{x:y}更多的屬性。
指定類型參數(shù):
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
// 報(bào)錯(cuò),因?yàn)楦鶕?jù)第一次參數(shù),Type會(huì)被識(shí)別為number,但是第二個(gè)參數(shù)卻是string[]類型。
const arr = combine([1, 2, 3], ["hello"]);
如果執(zhí)意這么設(shè)計(jì)函數(shù)的話,可以考慮使用聯(lián)合類型:
const arr = combine<string | number>([1, 2, 3], ["hello"]);
使用泛型函數(shù)的建議
-
盡可能使用類型參數(shù)本身,而不去使用類型約束。
// Good: 返回值類型會(huì)被推斷為Type function firstElement1<Type>(arr: Type[]) { return arr[0]; } // Bad: 返回值類型會(huì)被推斷為any function firstElement2<Type extends any[]>(arr: Type) { return arr[0]; } -
盡可能少地使用類型參數(shù)。
過(guò)多的類型參數(shù)會(huì)使得函數(shù)難以閱讀,盡量確保類型參數(shù)與多個(gè)值相關(guān)(例如與函數(shù)參數(shù)和返回值都有關(guān))再使用。
// Good: 只用了Type一個(gè)類型參數(shù) function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] { return arr.filter(func); } // Bad: Func這個(gè)類型參數(shù)是多余的,只用在了一個(gè)函數(shù)參數(shù) function filter2<Type, Func extends (arg: Type) => boolean>( arr: Type[], func: Func ): Type[] { return arr.filter(func); } -
如果類型參數(shù)只出現(xiàn)在一個(gè)位置,那么這個(gè)類型參數(shù)很可能不是必要的。
使用泛型是因?yàn)楹瘮?shù)中有若干個(gè)值的類型存在關(guān)聯(lián),如果類型參數(shù)只出現(xiàn)在一個(gè)位置,很可能不是必要的。
// Bad: Str是不必要的 function greet<Str extends string>(s: Str) { console.log("Hello, " + s); } // Good function greet(s: string) { console.log("Hello, " + s); }
可選參數(shù)列表
function f(x?: number) {
// ...
}
f(); // OK
f(10); // OK
注:上面的代碼中x的類型實(shí)際為number|undefined,當(dāng)不傳入該參數(shù)的時(shí)候就是undefined。
如果考慮設(shè)置默認(rèn)值,如下,那么x的類型就會(huì)變成number,排除了undefined的情況。
function f(x = 10) {
// ...
}
注:只要一個(gè)參數(shù)是可選的,那么這個(gè)參數(shù)就可以被傳入undefined。
回調(diào)函數(shù)的可選參數(shù)
在設(shè)計(jì)一個(gè)回調(diào)函數(shù)的函數(shù)類型時(shí),不要使用可選參數(shù)。
函數(shù)重載
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);
// 報(bào)錯(cuò)
const d3 = makeDate(1, 3);
如上述代碼,先寫兩個(gè)重載函數(shù)簽名(overload signatures),然后再寫一個(gè)函數(shù)兼容實(shí)現(xiàn)這兩個(gè)簽名,叫做實(shí)現(xiàn)簽名(implementation signature)。
在調(diào)用函數(shù)的時(shí)候,需要以重載簽名為標(biāo)準(zhǔn),不能以實(shí)現(xiàn)簽名為標(biāo)準(zhǔn)。也就是說(shuō),上面這段代碼中的函數(shù)makeDate,要么傳入1個(gè)參數(shù),要么傳入3個(gè)參數(shù),不能傳入2個(gè)參數(shù)。
注:
- 從外部無(wú)法看見實(shí)現(xiàn)的簽名。在編寫重載函數(shù)時(shí),應(yīng)該始終在函數(shù)的實(shí)現(xiàn)之上有兩個(gè)或多個(gè)簽名。
- 實(shí)現(xiàn)簽名與重載簽名之間要兼容。
- 當(dāng)可以使用聯(lián)合類型函數(shù)參數(shù)解決問題時(shí),就不要使用函數(shù)重載。
聲明this的類型
const db = getDB();
const admins = db.filterUsers(function (this: User) {
return this.admin;
});
其它與函數(shù)相關(guān)的類型
void
void作為函數(shù)的返回值類型,表示函數(shù)沒有返回值。
在 JS 中沒有返回值的函數(shù)會(huì)返回 undefined,但是在 TS 中undefined和void是不同的。
當(dāng)函數(shù)返回值聲明為void,仍可以在函數(shù)體中return內(nèi)容,但不管返回了什么值,最終接收函數(shù)返回值的那個(gè)變量都會(huì)是void類型。
type voidFunc = ()=>void;
const f: voidFunc = ()=> true;
// 這里value的類型會(huì)被推斷為void
const value = f();
object
object類型是除了string,number,bigint,boolean,symbol,null和 undefined的其它類型。
注:object類型與空對(duì)象類型{}不同,與全局類型Object也不同。永遠(yuǎn)不要使用Object類型,而是使用object。
在 JS 中,函數(shù)也是對(duì)象;在 TS 中,函數(shù)也被認(rèn)為是object類型。
unknown
unknown和any非常類似,但是unknown更安全。
因?yàn)?code>unknown類型變量的任何操作都是非法的,這迫使大多數(shù)操作之前需要對(duì)unknown類型變量進(jìn)行類型的檢查。
而any類型的值執(zhí)行操作之前不需要進(jìn)行任何檢查。
function f1(a: any) {
a.b(); // OK
}
function f2(a: unknown) {
a.b(); // ERROR: 'a' is of type 'unknown'.
}
注:unknown類型只能賦值給any或unknown類型。
unknown類型的意義:TS 不允許我們對(duì)類型為 unknown 的值執(zhí)行任意操作。我們必須首先執(zhí)行某種類型檢查以縮小我們正在使用的值的類型范圍。
可以使用類型收束(Narrowing)的操作將unknown縮小到具體的類型,再進(jìn)行后續(xù)操作。
never
never通常描述返回值類型,表示永遠(yuǎn)不返回值。與void不同,使用never意味著函數(shù)會(huì)拋出一個(gè)異常,或者程序會(huì)被終止。
function fail(msg: string): never {
throw new Error(msg);
}
另一種情況下也會(huì)出現(xiàn)never,就是當(dāng)聯(lián)合類型被不斷收窄到空時(shí),就是never:
function fn(x: string | number) {
if (typeof x === "string") {
// do something
} else if (typeof x === "number") {
// do something else
} else {
x; // 這里x的類型是'never'
}
}
Function
全局類型Function聲明的變量包含了 JS 中函數(shù)所擁有的所有屬性和方法,例如bind、call、apply。
被Function聲明的變量是可執(zhí)行的,并且返回any。
這種函數(shù)類型聲明方式很不安全,因?yàn)榉祷?code>any,最好使用函數(shù)類型表達(dá)式聲明:()=>void。
function doSomething(f: Function) {
return f(1, 2, 3);
}
不定長(zhǎng)參數(shù)列表
在 TS 中,不定長(zhǎng)參數(shù)列表的類型應(yīng)該被聲明為Array<T>、T[]或元組類型。
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x);
}
spread語(yǔ)法可以展開可迭代對(duì)象(例如數(shù)組,對(duì)象)變成不定長(zhǎng)的參數(shù)列表。例如push函數(shù)可以接收多個(gè)參數(shù)。
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);
但需要注意,TS 認(rèn)為數(shù)組是可變的,數(shù)組長(zhǎng)度是可變的。
觀察下面的案例代碼:
const args = [8, 5];
const angle = Math.atan2(...args);
盡管Math.atan2接收兩個(gè)number類型的參數(shù),而args也剛好是長(zhǎng)度為2的number[]類型數(shù)組,展開后剛好。
但是這段代碼會(huì)報(bào)錯(cuò),因?yàn)閿?shù)組是可變的。
一種較為直接的解決方法是使用const:
// 視為長(zhǎng)度為2的元組
const args = [8, 5] as const;
// 現(xiàn)在不會(huì)報(bào)錯(cuò)了
const angle = Math.atan2(...args);
參數(shù)解構(gòu)的類型聲明
function sum({ a, b, c }: { a: number; b: number; c: number }) {
console.log(a + b + c);
}
或者使用type簡(jiǎn)化:
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
console.log(a + b + c);
}

浙公網(wǎng)安備 33010602011771號(hào)