鍍金池/ 教程/ HTML/ 介紹
初始化項(xiàng)目結(jié)構(gòu)
聯(lián)合類(lèi)型
介紹
介紹
介紹
編譯選項(xiàng)
TypeScript 1.6
介紹
介紹
發(fā)展路線圖
介紹
在MSBuild里使用編譯選項(xiàng)
可迭代性
TypeScript 1.3
介紹
介紹
TypeScript 1.1
變量聲明
即將到來(lái)的Angular 2框架是使用TypeScript開(kāi)發(fā)的。 因此Angular和TypeScript一起使用非常簡(jiǎn)單方便
tsconfig.json
介紹
介紹
介紹
在MSBuild里使用編譯選項(xiàng)
使用TypeScript的每日構(gòu)建版本
新建工程
枚舉
三斜線指令
結(jié)合ASP.NET v5使用TypeScript
TypeScript里的this
介紹
TypeScript 1.4
編碼規(guī)范
介紹
模塊解析
ASP.NET 4
架構(gòu)概述
介紹
介紹
ASP.NET Core
TypeScript 1.8
介紹
介紹
創(chuàng)建簡(jiǎn)單工程
TypeScript 1.7
TypeScript 1.5
NPM包的類(lèi)型
支持TypeScript的編輯器

介紹

函數(shù)是JavaScript應(yīng)用程序的基礎(chǔ)。 它幫助你實(shí)現(xiàn)抽象層,模擬類(lèi),信息隱藏和模塊。 在TypeScript里,雖然已經(jīng)支持類(lèi),命名空間和模塊,但函數(shù)仍然是主要的定義行為的地方。 TypeScript為JavaScript函數(shù)添加了額外的功能,讓我們可以更容易地使用。

函數(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;
}

函數(shù)類(lèi)型

為函數(shù)定義類(lèi)型

讓我們?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)型,因此我們通常省略它。

書(shū)寫(xiě)完整函數(shù)類(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的一部分。

推斷類(lèi)型

嘗試這個(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)型。

可選參數(shù)和默認(rèn)參數(shù)

TypeScript里的每個(gè)函數(shù)參數(shù)都是必須的。 這不是指不能傳遞nullundefined作為參數(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ù)

必要參數(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;

Lambda表達(dá)式和使用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ò)誤。

上一篇:介紹下一篇:介紹