此頁面已棄用

此手冊頁面已被取代,前往新頁面

函式

函式是 JavaScript 中任何應用程式的基本建構區塊。它們是您建立抽象層級的方式,模擬類別、資訊隱藏和模組。在 TypeScript 中,雖然有類別、命名空間和模組,但函式仍然扮演著描述如何執行事情的重要角色。TypeScript 也為標準 JavaScript 函式新增了一些新功能,讓它們更容易使用。

函數

首先,就像在 JavaScript 中一樣,TypeScript 函數可以建立為命名函數或匿名函數。這讓您可以選擇最適合您應用程式的做法,無論您是要在 API 中建立函數清單,還是要建立一次性函數傳遞給其他函數。

快速回顧一下這兩種做法在 JavaScript 中的樣子

ts
// Named function
function add(x, y) {
return x + y;
}
 
// Anonymous function
let myAdd = function (x, y) {
return x + y;
};
Try

就像在 JavaScript 中一樣,函數可以參考函數主體之外的變數。當它們這麼做時,它們被說成擷取這些變數。雖然了解這如何運作(以及使用此技術的權衡)超出了本文的範圍,但堅定地了解此機制如何運作是使用 JavaScript 和 TypeScript 的重要部分。

ts
let z = 100;
 
function addToZ(x, y) {
return x + y + z;
}
Try

函數類型

輸入函數

讓我們將類型新增到我們先前簡單的範例

ts
function add(x: number, y: number): number {
return x + y;
}
 
let myAdd = function (x: number, y: number): number {
return x + y;
};
Try

我們可以將類型新增到每個參數,然後新增到函數本身以新增傳回類型。TypeScript 可以透過查看傳回陳述來找出傳回類型,因此在許多情況下,我們也可以選擇將其省略。

撰寫函數類型

在我們鍵入函式後,讓我們透過檢視函式類型的每個部分,寫出函式的完整類型。

ts
let myAdd: (x: number, y: number) => number = function (
x: number,
y: number
): number {
return x + y;
};
Try

函式的類型具有相同的兩個部分:參數的類型和回傳類型。在寫出整個函式類型時,需要兩個部分。我們寫出參數類型就像參數清單一樣,為每個參數指定名稱和類型。此名稱只是為了幫助可讀性。我們也可以寫成

ts
let myAdd: (baseValue: number, increment: number) => number = function (
x: number,
y: number
): number {
return x + y;
};
Try

只要參數類型對齊,無論您在函式類型中為參數指定的任何名稱,都視為函式的有效類型。

第二部分是回傳類型。我們使用參數和回傳類型之間的箭頭 (=>) 來明確回傳類型。如前所述,這是函式類型中必要的部份,因此如果函式不回傳值,您應該使用 void 而不是省略它。

請注意,只有參數和回傳類型組成函式類型。擷取的變數不會反映在類型中。實際上,擷取的變數是任何函式的「隱藏狀態」的一部分,並不構成其 API。

推論類型

在使用範例時,你可能會注意到,即使你只在等號的一邊有型別,TypeScript 編譯器也能找出型別

ts
// The parameters 'x' and 'y' have the type number
let myAdd = function (x: number, y: number): number {
return x + y;
};
 
// myAdd has the full function type
let myAdd2: (baseValue: number, increment: number) => number = function (x, y) {
return x + y;
};
Try

這稱為「脈絡型別」,是一種型別推論的形式。這有助於減少讓你的程式維持型別所需的努力。

選用和預設參數

在 TypeScript 中,每個參數都假設函式需要。這並不表示不能給予 nullundefined,而是表示在呼叫函式時,編譯器會檢查使用者是否已為每個參數提供值。編譯器也假設這些參數是傳遞給函式的唯一參數。簡而言之,傳遞給函式的引數數量必須與函式預期的參數數量相符。

ts
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
 
let result1 = buildName("Bob"); // error, too few parameters
Expected 2 arguments, but got 1.2554Expected 2 arguments, but got 1.
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
Expected 2 arguments, but got 3.2554Expected 2 arguments, but got 3.
let result3 = buildName("Bob", "Adams"); // ah, just right
Try

在 JavaScript 中,每個參數都是選用的,使用者可以視需要略過。略過時,其值為 undefined。我們可以在 TypeScript 中加入 ? 到我們想要設為選用的參數尾端,以取得此功能。例如,假設我們想要將上述的姓氏參數設為選用

ts
function buildName(firstName: string, lastName?: string) {
if (lastName) return firstName + " " + lastName;
else return firstName;
}
 
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
Expected 1-2 arguments, but got 3.2554Expected 1-2 arguments, but got 3.
let result3 = buildName("Bob", "Adams"); // ah, just right
Try

任何選用參數都必須在必要參數之後。如果我們想讓名字變成選用,而不是姓氏,我們需要變更函數中參數的順序,將名字放在清單中的最後。

在 TypeScript 中,我們也可以設定一個值,如果使用者沒有提供,或是在其位置傳遞 undefined,則會指派給參數。這些稱為預設初始化參數。我們來看看前一個範例,並將姓氏預設為 "Smith"

ts
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
 
let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
Expected 1-2 arguments, but got 3.2554Expected 1-2 arguments, but got 3.
let result4 = buildName("Bob", "Adams"); // ah, just right
Try

所有必要參數之後的預設初始化參數會被視為選用,就像選用參數一樣,在呼叫其各自函數時可以省略。這表示選用參數和尾隨預設參數在其類型中會共用,因此兩者

ts
function buildName(firstName: string, lastName?: string) {
// ...
}

ts
function buildName(firstName: string, lastName = "Smith") {
// ...
}

共用相同的類型 (firstName: string, lastName?: string) => stringlastName 的預設值會消失在類型中,只留下參數為選用的事實。

與一般選用參數不同,預設初始化參數不需要在必要參數之後。如果預設初始化參數在必要參數之前,使用者需要明確傳遞 undefined 以取得預設初始化值。例如,我們可以用僅在 firstName 上有預設初始化值的方式撰寫最後一個範例

ts
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
 
let result1 = buildName("Bob"); // error, too few parameters
Expected 2 arguments, but got 1.2554Expected 2 arguments, but got 1.
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
Expected 2 arguments, but got 3.2554Expected 2 arguments, but got 3.
let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"
Try

Rest 參數

必要的、可選的和預設的參數都有個共通點:它們一次只討論一個參數。有時,您想要將多個參數當成一個群組處理,或者您可能不知道一個函式最終會使用多少個參數。在 JavaScript 中,您可以使用每個函式主體中可見的 arguments 變數直接處理參數。

在 TypeScript 中,您可以將這些參數收集到一個變數中

ts
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
 
// employeeName will be "Joseph Samuel Lucas MacKinzie"
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
Try

Rest 參數被視為無限數量的可選參數。傳遞 rest 參數的參數時,您可以使用任意多個參數;您甚至可以不傳遞任何參數。編譯器會建立一個傳遞參數的陣列,其名稱在省略符號 (...) 之後給出,讓您可以在函式中使用它。

省略符號也用於具有 rest 參數的函式類型中

ts
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
 
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
Try

this

學習如何在 JavaScript 中使用 this 算是某種入門儀式。由於 TypeScript 是 JavaScript 的超集,因此 TypeScript 開發人員也需要學習如何使用 this,以及如何找出它未正確使用的時機。幸運的是,TypeScript 允許您使用幾種技術來找出 this 的不正確用法。不過,如果您需要學習 this 在 JavaScript 中如何運作,請先閱讀 Yehuda Katz 的 了解 JavaScript 函式呼叫與「this」。Yehuda 的文章很好地解釋了 this 的內部運作,因此我們在此僅介紹基礎知識。

this 和箭頭函式

在 JavaScript 中,this 是在呼叫函式時設定的變數。這使得它成為一個非常強大且靈活的功能,但代價是必須隨時了解函式執行的內容。這顯然令人困惑,尤其是在傳回函式或將函式傳遞為引數時。

讓我們來看一個範例

ts
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function () {
return function () {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
 
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
},
};
 
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
 
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
Try

請注意,createCardPicker 是一個函式,它本身會傳回一個函式。如果我們嘗試執行此範例,我們會收到一個錯誤,而不是預期的警示方塊。這是因為在 createCardPicker 所建立的函式中所使用的 this 會設定為 window,而不是我們的 deck 物件。這是因為我們自行呼叫 cardPicker()。像這樣最上層的非方法語法呼叫會使用 window 作為 this。(注意:在嚴格模式下,this 會是 undefined,而不是 window)。

我們可以透過確保函式在傳回函式以供稍後使用之前,已繫結到正確的 this 來修正此問題。這樣一來,不論稍後如何使用它,它仍能看到原始的 deck 物件。為此,我們變更函式表示式以使用 ECMAScript 6 箭號語法。箭號函式會擷取函式建立的位置,而不是呼叫函式的位置的 this

ts
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function () {
// NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
 
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
},
};
 
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
 
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
Try

更好的是,如果您將 noImplicitThis 旗標傳遞給編譯器,TypeScript 會在您犯下此錯誤時警告您。它會指出 this.suits[pickedSuit] 中的 thisany 類型。

this 參數

不幸的是,this.suits[pickedSuit] 的類型仍然是 any。這是因為 this 來自物件文字內的函式表達式。若要修正這個問題,你可以提供明確的 this 參數。this 參數是假的參數,出現在函式參數清單的第一個

ts
function f(this: void) {
// make sure `this` is unusable in this standalone function
}

讓我們在上面的範例中新增幾個介面,CardDeck,以讓類型更清楚且更容易重複使用

ts
interface Card {
suit: string;
card: number;
}
 
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
 
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function (this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
 
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
},
};
 
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
 
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
Try

現在 TypeScript 知道 createCardPicker 預期會在 Deck 物件上呼叫。這表示 this 現在是 Deck 類型,而不是 any,因此 noImplicitThis 不會造成任何錯誤。

this 參數在回呼函式中

當您將函式傳遞給稍後會呼叫函式的函式庫時,您也可能會在回呼中遇到 this 錯誤。由於呼叫回呼的函式庫會像一般函式一樣呼叫它,因此 this 將會是 undefined。透過一些工作,您也可以使用 this 參數來防止回呼錯誤。首先,函式庫作者需要使用 this 來註解回呼類型

ts
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
Try

this: void 表示 addClickListener 預期 onclick 是不需要 this 類型的函式。其次,使用 this 註解您的呼叫程式碼

ts
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used `this` here. using this callback would crash at runtime
this.info = e.message;
}
}
 
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!
Argument of type '(this: Handler, e: Event) => void' is not assignable to parameter of type '(this: void, e: Event) => void'. The 'this' types of each signature are incompatible. Type 'void' is not assignable to type 'Handler'.2345Argument of type '(this: Handler, e: Event) => void' is not assignable to parameter of type '(this: void, e: Event) => void'. The 'this' types of each signature are incompatible. Type 'void' is not assignable to type 'Handler'.
Try

使用 this 註解後,您明確表示必須在 Handler 的執行個體上呼叫 onClickBad。然後 TypeScript 將會偵測到 addClickListener 需要具有 this: void 的函式。若要修正錯誤,請變更 this 的類型

ts
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use `this` here because it's of type void!
console.log("clicked!");
}
}
 
let h = new Handler();
uiElement.addClickListener(h.onClickGood);
Try

由於 onClickGood 將其 this 類型指定為 void,因此可以傳遞給 addClickListener。當然,這也表示它無法使用 this.info。如果您想要同時使用這兩個,則必須使用箭頭函式

ts
class Handler {
info: string;
onClickGood = (e: Event) => {
this.info = e.message;
};
}
Try

這會運作,因為箭頭函式使用外部 this,因此您隨時可以將它們傳遞給預期 this: void 的函式。缺點是會為 Handler 類型的每個物件建立一個箭頭函式。另一方面,方法只會建立一次並附加到 Handler 的原型。它們會在 Handler 類型的所有物件之間共用。

重載

JavaScript 本質上是一種非常動態的語言。單一 JavaScript 函式根據傳入參數的形狀傳回不同類型的物件,這並非罕見。

ts
let suits = ["hearts", "spades", "clubs", "diamonds"];
 
function pickCard(x: any): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
 
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
 
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
 
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
Try

在此,pickCard 函式會根據使用者傳入的內容傳回兩種不同的東西。如果使用者傳入代表牌組的物件,函式會挑選卡片。如果使用者挑選卡片,我們會告訴他們挑選了哪張卡片。但我們要如何向類型系統說明這一點?

答案是為同一函式提供多個函式類型作為重載清單。此清單是編譯器用來解析函式呼叫的內容。讓我們建立一個重載清單,說明我們的 pickCard 接受什麼以及傳回什麼。

ts
let suits = ["hearts", "spades", "clubs", "diamonds"];
 
function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
 
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
 
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
 
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
Try

有了這個變更,重載現在會提供類型檢查的呼叫給 pickCard 函式。

為了讓編譯器挑選正確的類型檢查,它會遵循與底層 JavaScript 類似的程序。它會查看重載清單,並從第一個重載開始,嘗試使用提供的參數呼叫函式。如果找到符合的項目,它會挑選這個重載作為正確的重載。因此,慣例是將重載從最具體的排序到最不具體的。

請注意,function pickCard(x): any 部分並非重載清單的一部分,所以它只有兩個重載:一個接收物件,一個接收數字。使用任何其他參數類型呼叫 pickCard 會導致錯誤。

TypeScript 文件是一個開源專案。透過 傳送 Pull Request ❤ 來幫助我們改善這些頁面

此頁面的貢獻者
RCRyan Cavanaugh (55)
DRDaniel Rosenwasser (23)
OTOrta Therox (18)
NSNathan Shively-Sanders (4)
MFMartin Fischer (1)
24+

最後更新:2024 年 3 月 21 日