之前我們有簡單瞭解了一下使用接口表示函數類型。接下來我們瞭解 typescript 中的函數和泛型。
函數
//聲明式
function add(x, y) {
return x + y;
}
// 函數表達式
let myAdd = function(x, y) {
return x + y;
};
JavaScript 聲明方式有聲明式和表達式,另外有具名函數和匿名函數:
// 匿名函數
(function() {
let a = 20;
console.log(a);
})();
typescript 除了在 JavaScript 基礎上添加了一系列校驗外其它沒什麼很大區別。
typescript 函數類型:
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number {
return x + y;
};
//完整類型
let myAdd: (x: number, y: number) => number = function(
x: number,
y: number
): number {
return x + y;
};
// 函數的參數名稱不需要與參數類型定義時的名稱一致
let myAdd: (baseValue: number, increment: number) => number = function(
x: number,
y: number
): number {
return x + y;
};
這裏我們隊函數的參數和返回值指定了 number 類型的校驗。TypeScript 能夠根據返回語句自動推斷出返回值類型,因此我們可以省略它。完整函數類型等於號之前的分別爲參數和返回值的類型校驗,有點類似寫箭頭函數()=>
,=>箭頭代表返回。函數的參數名稱不需要和參數類型定義時得名稱一致。
let myAdd = function(x: number, y: number): number {
return x + y;
};
// 當指定了參數類型定義但 待校驗函數參數沒有標註類型時,typescript會自動識別類型
let myAdd: (baseValue: number, increment: number) => number = function(x, y) {
return x + y;
};
當指定了參數類型定義但 待校驗函數參數沒有標註類型時,typescript 會自動識別類型,按照類型定義規則校驗。
JavaScript 裏函數的參數是可選的定義之後可傳可不傳,沒有傳參的時候是 undefined,例如:
function aa(name, age) {
// do something
}
但是 typescript 裏一旦表明這個函數有參數,那麼在調用這個函數的時候必須加上參數。如果我們想要添加可選參數,可以使用可選類型:
// 可選參數必須跟在必須參數後面。 如果我們想讓first name是可選的,那麼就必須調整它們的位置,把first name放在後面
function buildName(firstName: string, lastName?: string) {
if (lastName){
return firstName + " " + lastName;
} else{
else return firstName;
}
}
我們也可以直接像 ES6 一樣給參數一個默認值:
// 當調用函數沒有傳參數lastName時,lastName的默認值爲Smith
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
與普通可選參數不同的是,帶默認值的參數不需要放在必須參數的後面。 如果帶默認值的參數出現在必須參數前面,用戶必須明確的傳入undefined
值來獲得默認值——————這裏其實意思是(如下面栗子)當我們將默認參數寫在必需參數的前面,調用函數只傳了一個參數那麼這個參數對應的是 firstName,而 lastName 是必需的相當於沒有 lastName 的參數,所以報錯。所以如果想要Bob
對應 lastName 的值,就需要在第一個參數的位置傳入undefined
。
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); //An argument for 'lastName' was not provided.
let result4 = buildName(undefined, "Bob"); // "Will Bob"
對於不知道會有多少個參數的函數,我們也可以像 ES6 一樣添加使用擴展符表示:
// 使用... 擴展符,表示可展開的數據
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
在 JavaScript 中,this 的指向與函數被調用的上下文有關係,這使得我們不太好區分。例如:
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
suits: 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);
這個例子會報錯,我們來解析一下,deck
是一個對象,他包含有 suits、suits
屬性和 createCardPicker
方法。createCardPicker
方法返回了一個匿名函數。let cardPicker = deck.createCardPicker()
這句意思是拿到createCardPicker
的返回函數,let pickedCard = cardPicker();
這句的意思是執行匿名函數並且拿到該函數的返回對象。
前面提到了在 JavaScript 中,this 的指向與函數被調用的上下文有關係,在上面的例子中,調用匿名函數的是 window/global,相當於以下函數:
function cardPicker() {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
}
cardPicker();
在 window/global 中找不到suits
對象,所以報錯。
這時,JavaScript 提供了一系列改變函數執行上下文的方法:call、apply、bind。我們就可以修改上述例子:
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.call(deck); //手動將cardPicker函數(也就是createCardPicker返回的匿名函數)中的this指向deck對象。
console.log("card: " + pickedCard.card + " of " + pickedCard.suit); // card: 7 of diamonds
也可以在定義這個函數的時候就綁定該函數環境的 this:
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
let that = this; // 在這裏使用that變量保存 this
return function() {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: that.suits[pickedSuit], card: pickedCard % 13 };
};
}
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit); // card: 12 of spades
對於函數 this 指向問題,ES6 提供了箭頭函數,能夠能保存函數創建時的 this,被調用的時候不會改變 this 指向:
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
// 使用箭頭函數
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
}
};
在使用 typescript 的時候,this 默認爲 any 類型,如果想要規定 this 的類型,可以寫成如下:
// Card接口
interface Card {
suit: string;
card: number;
}
// Deck接口
interface Deck {
suits: string[];
cards: number[];
// 函數類型接口
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// createCardPicker函數 this類型爲Deck,返回值類型爲Card
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 };
};
}
};
泛型
平常我們不使用 typescript 進行類型判斷的時候,函數的返回值類型與我們傳給函數的參數類型不一定相同,可能會因爲我們不合理的操作導致返回值類型不是傳入值類型。
平常寫法,只能傳入是 number,返回是 number,靈活性不好:
function identity(arg: number): number {
return arg;
}
使用 any,any 代表接收任意類型,那和不設置 typescript 差不多,傳入參數類型值和返回類型值不一定相同:
function identity(arg: any): any {
return arg;
}
爲了使返回值的類型與傳入參數的類型相同,typescript 可以使用類型變量
來達到目的:
// 泛型函數
function identity<T>(arg: T): T {
return arg;
}
類型名稱可以不相同:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <U>(arg: U) => U = identity;
此時 T 代表可以是任意類型的數據,但因爲 T 可以是任意類型數據,所以得注意如果使用了一些特定類型纔有的屬性,就會報錯:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // error,由於T可以是任意類型數據,有些類型是沒有長度屬性的,比如number
return arg;
}
數組:
這樣傳入時數組爲任意類型,返回時爲傳入時的類型,且數組有 length 屬性
function foo<T>(arg: T[]): T[] {
console.log(arg.length);
return arg;
}
也可以寫成:
function foo<T>(arg: Array<T>): Array<T> {
return arg;
}
類型是一個對象字面量:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: { <U>(arg: U): U } = identity;
泛型接口,和普通接口寫起來差不多,只是將類型換成了類型變量:
interface GenericIdentityFn {
<U>(arg: U): U;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
泛型類,和直接寫類差不多,類名稱後面加個變量類型,其他類型也換成變量類型:
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>(); // 明確GenericNumber接口類型爲number
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };