函數(shù)是JavaScript應(yīng)用程序的基礎(chǔ)。 它幫助你實(shí)現(xiàn)抽象層,模擬類(lèi),信息隱藏和模塊。 在TypeScript里,雖然已經(jīng)支持類(lèi),命名空間和模塊,但函數(shù)仍然是主要的定義行為的地方。 TypeScript為JavaScript函數(shù)添加了額外的功能,讓我們可以更容易地使用。
和JavaScript一樣,TypeScript函數(shù)可以創(chuàng)建有名字的函數(shù)和匿名函數(shù)。 你可以隨意選擇適合應(yīng)用程序的方式,不論是定義一系列API函數(shù)還是只使用一次的函數(shù)。
通過(guò)下面的例子可以迅速回想起這兩種JavaScript中的函數(shù):
// Named function
function add(x, y) {
return x + y;
}
// Anonymous function
let myAdd = function(x, y) { return x + y; };
在JavaScript里,函數(shù)可以使用函數(shù)體外部的變量。 當(dāng)函數(shù)這么做時(shí),我們說(shuō)它‘捕獲’了這些變量。 至于為什么可以這樣做以及其中的利弊超出了本文的范圍,但是深刻理解這個(gè)機(jī)制對(duì)學(xué)習(xí)JavaScript和TypeScript會(huì)很有幫助。
let z = 100;
function addToZ(x, y) {
return x + y + z;
}
讓我們?yōu)樯厦婺莻€(gè)函數(shù)添加類(lèi)型:
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number { return x+y; };
我們可以給每個(gè)參數(shù)添加類(lèi)型之后再為函數(shù)本身添加返回值類(lèi)型。 TypeScript能夠根據(jù)返回語(yǔ)句自動(dòng)推斷出返回值類(lèi)型,因此我們通常省略它。
現(xiàn)在我們已經(jīng)為函數(shù)指定了類(lèi)型,下面讓我們寫(xiě)出函數(shù)的完整類(lèi)型。
let myAdd: (x:number, y:number)=>number =
function(x: number, y: number): number { return x+y; };
函數(shù)類(lèi)型包含兩部分:參數(shù)類(lèi)型和返回值類(lèi)型。 當(dāng)寫(xiě)出完整函數(shù)類(lèi)型的時(shí)候,這兩部分都是需要的。 我們以參數(shù)列表的形式寫(xiě)出參數(shù)類(lèi)型,為每個(gè)參數(shù)指定一個(gè)名字和類(lèi)型。 這個(gè)名字只是為了增加可讀性。 我們也可以這么寫(xiě):
let myAdd: (baseValue:number, increment:number) => number =
function(x: number, y: number): number { return x + y; };
只要參數(shù)類(lèi)型是匹配的,那么就認(rèn)為它是有效的函數(shù)類(lèi)型,而不在乎參數(shù)名是否正確。
第二部分是返回值類(lèi)型。
對(duì)于返回值,我們?cè)诤瘮?shù)和返回值類(lèi)型之前使用(=>
)符號(hào),使之清晰明了。
如之前提到的,返回值類(lèi)型是函數(shù)類(lèi)型的必要部分,如果函數(shù)沒(méi)有返回任何值,你也必須指定返回值類(lèi)型為void
而不能留空。
函數(shù)的類(lèi)型只是由參數(shù)類(lèi)型和返回值組成的。 函數(shù)中使用的捕獲變量不會(huì)體現(xiàn)在類(lèi)型里。 實(shí)際上,這些變量是函數(shù)的隱藏狀態(tài)并不是組成API的一部分。
嘗試這個(gè)例子的時(shí)候,你會(huì)發(fā)現(xiàn)如果你在賦值語(yǔ)句的一邊指定了類(lèi)型但是另一邊沒(méi)有類(lèi)型的話,TypeScript編譯器會(huì)自動(dòng)識(shí)別出類(lèi)型:
// myAdd has the full function type
let myAdd = function(x: number, y: number): number { return x + y; };
// The parameters `x` and `y` have the type number
let myAdd: (baseValue:number, increment:number) => number =
function(x, y) { return x + y; };
這叫做“按上下文歸類(lèi)”,是類(lèi)型推論的一種。 它幫助我們更好地為程序指定類(lèi)型。
TypeScript里的每個(gè)函數(shù)參數(shù)都是必須的。
這不是指不能傳遞null
或undefined
作為參數(shù),而是說(shuō)編譯器檢查用戶是否為每個(gè)參數(shù)都傳入了值。
編譯器還會(huì)假設(shè)只有這些參數(shù)會(huì)被傳遞進(jìn)函數(shù)。
簡(jiǎn)短地說(shuō),傳遞給一個(gè)函數(shù)的參數(shù)個(gè)數(shù)必須與函數(shù)期望的參數(shù)個(gè)數(shù)一致。
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right
JavaScript里,每個(gè)參數(shù)都是可選的,可傳可不傳。
沒(méi)傳參的時(shí)候,它的值就是undefined。
在TypeScript里我們可以在參數(shù)名旁使用?
實(shí)現(xiàn)可選參數(shù)的功能。
比如,我們想讓last name是可選的:
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
let result3 = buildName("Bob", "Adams"); // ah, just right
可選參數(shù)必須跟在必須參數(shù)后面。 如果上例我們想讓first name是可選的,那么就必須調(diào)整它們的位置,把first name放在后面。
在TypeScript里,我們也可以為參數(shù)提供一個(gè)默認(rèn)值當(dāng)用戶沒(méi)有傳遞這個(gè)參數(shù)或傳遞的值是undefined
時(shí)。
它們叫做有默認(rèn)初始化值的參數(shù)。
讓我們修改上例,把last name的默認(rèn)值設(shè)置為"Smith"
。
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
let result4 = buildName("Bob", "Adams"); // ah, just right
在所有必須參數(shù)后面的帶默認(rèn)初始化的參數(shù)都是可選的,與可選參數(shù)一樣,在調(diào)用函數(shù)的時(shí)候可以省略。 也就是說(shuō)可選參數(shù)與末尾的默認(rèn)參數(shù)共享參數(shù)類(lèi)型。
function buildName(firstName: string, lastName?: string) {
// ...
}
和
function buildName(firstName: string, lastName = "Smith") {
// ...
}
共享同樣的類(lèi)型(firstName: string, lastName?: string) => string
。
默認(rèn)參數(shù)的默認(rèn)值消失了,只保留了它是一個(gè)可選參數(shù)的信息。
與普通可選參數(shù)不同的是,帶默認(rèn)值的參數(shù)不需要放在必須參數(shù)的后面。
如果帶默認(rèn)值的參數(shù)出現(xiàn)在必須參數(shù)前面,用戶必須明確的傳入undefined
值來(lái)獲得默認(rèn)值。
例如,我們重寫(xiě)最后一個(gè)例子,讓firstName
是帶默認(rèn)值的參數(shù):
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"
必要參數(shù),默認(rèn)參數(shù)和可選參數(shù)有個(gè)共同點(diǎn):它們表示某一個(gè)參數(shù)。
有時(shí),你想同時(shí)操作多個(gè)參數(shù),或者你并不知道會(huì)有多少參數(shù)傳遞進(jìn)來(lái)。
在JavaScript里,你可以使用arguments
來(lái)訪問(wèn)所有傳入的參數(shù)。
在TypeScript里,你可以把所有參數(shù)收集到一個(gè)變量里:
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
剩余參數(shù)會(huì)被當(dāng)做個(gè)數(shù)不限的可選參數(shù)。
可以一個(gè)都沒(méi)有,同樣也可以有任意個(gè)。
編譯器創(chuàng)建參數(shù)數(shù)組,名字是你在省略號(hào)(...
)后面給定的名字,你可以在函數(shù)體內(nèi)使用這個(gè)數(shù)組。
這個(gè)省略號(hào)也會(huì)在帶有剩余參數(shù)的函數(shù)類(lèi)型定義上使用到:
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
this
JavaScript里this
的工作機(jī)制對(duì)JavaScript程序員來(lái)說(shuō)已經(jīng)是老生常談了。
的確,學(xué)會(huì)如何使用它絕對(duì)是JavaScript編程中的一件大事。
由于TypeScript是JavaScript的超集,TypeScript程序員也需要弄清this
工作機(jī)制并且當(dāng)有bug的時(shí)候能夠找出錯(cuò)誤所在。
this
的工作機(jī)制可以單獨(dú)寫(xiě)一本書(shū)了,并確已有人這么做了。在這里,我們只介紹一些基礎(chǔ)知識(shí)。
JavaScript里,this
的值在函數(shù)被調(diào)用的時(shí)候才會(huì)指定。
這是個(gè)既強(qiáng)大又靈活的特點(diǎn),但是你需要花點(diǎn)時(shí)間弄清楚函數(shù)調(diào)用的上下文是什么。
眾所周知這不是一件很簡(jiǎn)單的事,特別是函數(shù)當(dāng)做回調(diào)函數(shù)使用的時(shí)候。
下面看一個(gè)例子:
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);
如果我們運(yùn)行這個(gè)程序,會(huì)發(fā)現(xiàn)它并沒(méi)有彈出對(duì)話框而是報(bào)錯(cuò)了。
因?yàn)?code>createCardPicker返回的函數(shù)里的this
被設(shè)置成了window
而不是deck
對(duì)象。
當(dāng)你調(diào)用cardPicker()
時(shí)會(huì)發(fā)生這種情況。這里沒(méi)有對(duì)this
進(jìn)行動(dòng)態(tài)綁定因此為window。(注意在嚴(yán)格模式下,會(huì)是undefined而不是window)。
為了解決這個(gè)問(wèn)題,我們可以在函數(shù)被返回時(shí)就綁好正確的this
。
這樣的話,無(wú)論之后怎么使用它,都會(huì)引用綁定的‘deck’對(duì)象。
我們把函數(shù)表達(dá)式變?yōu)槭褂胠ambda表達(dá)式( () => {} )。 這樣就會(huì)在函數(shù)創(chuàng)建的時(shí)候就指定了‘this’值,而不是在函數(shù)調(diào)用的時(shí)候。
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
// Notice: the line below is now a lambda, allowing us to capture `this` earlier
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);
為了解更多關(guān)于this
的信息,請(qǐng)閱讀Yahuda Katz的Understanding JavaScript Function Invocation and "this"。
JavaScript本身是個(gè)動(dòng)態(tài)語(yǔ)言。 JavaScript里函數(shù)根據(jù)傳入不同的參數(shù)而返回不同類(lèi)型的數(shù)據(jù)是很常見(jiàn)的。
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x): 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);
pickCard
方法根據(jù)傳入?yún)?shù)的不同會(huì)返回兩種不同的類(lèi)型。
如果傳入的是代表紙牌的對(duì)象,函數(shù)作用是從中抓一張牌。
如果用戶想抓牌,我們告訴他抓到了什么牌。
但是這怎么在類(lèi)型系統(tǒng)里表示呢。
方法是為同一個(gè)函數(shù)提供多個(gè)函數(shù)類(lèi)型定義來(lái)進(jìn)行函數(shù)重載。
編譯器會(huì)根據(jù)這個(gè)列表去處理函數(shù)的調(diào)用。
下面我們來(lái)重載pickCard
函數(shù)。
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 {
// 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);
這樣改變后,重載的pickCard
函數(shù)在調(diào)用的時(shí)候會(huì)進(jìn)行正確的類(lèi)型檢查。
為了讓編譯器能夠選擇正確的檢查類(lèi)型,它與JavaScript里的處理流程相似。 它查找重載列表,嘗試使用第一個(gè)重載定義。 如果匹配的話就使用這個(gè)。 因此,在定義重載的時(shí)候,一定要把最精確的定義放在最前面。
注意,function pickCard(x): any
并不是重載列表的一部分,因此這里只有兩個(gè)重載:一個(gè)是接收對(duì)象另一個(gè)接收數(shù)字。
以其它參數(shù)調(diào)用pickCard
會(huì)產(chǎn)生錯(cuò)誤。