TypeScript Function(函數)

在JavaScript中,函數是構成任何應用程序的基礎塊。經過函數,你得以實現創建抽象層、模仿類、信息隱藏和模塊化。在TypeScript中,雖然已經存在類和模塊化,可是函數依舊在如何去"處理"事件的問題上起關鍵做用。TypeScript在JavaScript的標準基礎上給函數添加了一些新的功能使使用者能夠更好的用函數處理工做。javascript

函數java

首先,和JavaScript同樣,TypeScript中的函數能夠建立命名函數和匿名函數。這樣你就能夠爲應用程序選擇最合適的方式,不管是定義一系列函數API仍是一次性使用的函數。程序員

快速的回顧下JavaScript中的這兩種方法是怎麼樣的:數組

// 命名函數
function add(x, y) {
    return x+y;
}
// 匿名函數
var myAdd = function(x, y) { return x+y; };

在JavaScript中,函數可使用函數外部的變量。當這麼作的時候,咱們稱之爲"捕獲"這些變量。要理解它是怎麼工做和衡量什麼時候使用這項技術,這已經超出本文的內容範圍了,透徹的理解這個機制在JavaScript和TypeScript中是多麼重要的一部分是須要的。dom

var z = 100;
function addToZ(x, y) {
    return x+y+z;
}

函數類型模塊化

給函數添加類型函數

爲以前的簡單案例添加類型:ui

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

咱們能夠給每一個參數指定類型,而且爲函數自己return的值指定類型。TypeScript可以根據return語句推算出返回值的類型,因此不少狀況下能夠忽略它。this

編寫函數類型spa

如今咱們已經爲函數添加了類型,接下來爲函數寫出全部的類型:

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

函數類型包括兩個部分:arguments(參數)的類型和return(返回)值的類型。當須要寫全部的函數類型,這兩部分是必需的。咱們寫參數類型就像寫一個參數列表同樣,每一個參數給定一個名稱和類型。這個名稱只是爲了增長可讀性,咱們能夠這樣寫:

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

只要參數類型正確,它就被認爲是有效的函數類型,而不用去在意參數名稱是否正確。

第二部分是返回值類型。咱們在參數和返回值類型之間用肥肥的箭頭(=>)來明確這個類型。正如前面所說,這只是函數類型的一部分,因此當不存在返回值時,應該使用"void",而不是任由它空着。

注:只有參數和返回值的類型組成了函數類型。捕獲到的變量不會在類型中體現。實際上,捕獲的變量屬於函數"隱藏狀態"部分的,而且也不是API的組成部分。

類型推斷

在例子中,你可能已經注意到,當你在賦值語句的任意一邊指定類型,TypeScript編譯器都可以在另外一邊自動識別類型:

// myAdd 函數中全部的類型
var myAdd = function(x: number, y: number): number { return x+y; };

// 參數'x'和'y'是number類型
var myAdd: (baseValue:number, increment:number)=>number = 
    function(x, y) { return x+y; };

這稱爲"上下文(語境)歸類",一種類型推斷的形式。這有助於減小工做量而且保持程序的類型。

可選參數和默認參數

和JavaScript不一樣,TypeScript函數中的每一個參數都是必需的。這並不意味着不能夠傳入"null"值,當一個函數被調用的時候,編譯器會檢查用戶是否爲每個參數提供值。編譯器也會假設這些參數就是須要被傳入函數的參數。簡而言之,函數的傳入參數的個數必須和函數所指望被傳入參數的個數相等。

function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}

var result1 = buildName("Bob");  // 錯誤,傳的參數太少
var result2 = buildName("Bob", "Adams", "Sr.");  // 錯誤,傳的參數太多
var result3 = buildName("Bob", "Adams");  // 額,這是正確的

在JavaScript中,每個參數都是可選的,用戶能夠在恰當的時候不用傳某個參數。這樣作就至關於傳入"undefined"代替這個參數。在TypeScript中,咱們能夠在參數後面加上"?"符號,讓這個參數變成可選參數。例如,咱們想要"lastName"是可選的:

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

var result1 = buildName("Bob");  // 正常運行
var result2 = buildName("Bob", "Adams", "Sr.");  // 錯誤,傳的參數太多
var result3 = buildName("Bob", "Adams");  // 額,這是正確的

可選參數必須放在必需參數後面(存在必需參數的狀況下)。假如咱們要"firstName"變成可選參數而不是"lastName",咱們須要改變函數參數的排序,將"firstName"放到後面。

在TypeScript中也能夠爲某個參數設置值,當用戶未提供該參數時,將使用這個值。這被稱爲"默認值"。將上個例子中的"lastName"的默認值設置爲"Smith":

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}

var result1 = buildName("Bob");  // 正常運行
var result2 = buildName("Bob", "Adams", "Sr.");  // 錯誤,傳的參數太多
var result3 = buildName("Bob", "Adams");  // 額,這是正確的

和可選參數同樣,默認參數必須放在必需參數後面(存在必需參數的狀況下)。

可選參數和默認參數共享類型。如

function buildName(firstName: string, lastName?: string) {

function buildName(firstName: string, lastName = "Smith") {

共享同一個類型 "(firstName: string, lastName?: string)=>string"。

其餘參數

必需參數,可選參數和默認參數都有以個共同點:它們只表示一個參數。有些時候可能想要多個參數,或者不知道具體有多少個參數最終須要被傳入。在JavaScript中,你可使用arguments來訪問函數傳入的全部參數。

在TypeScript中,你能夠將全部的參數彙集到一個變量中:

function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}

var employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

其餘參數被視爲數量無限的可選參數。你能夠一個都不傳,也能夠傳任意你想傳的個數。編譯器將會生成一個你傳入函數的參數數組,以省略號("...")以後的名稱命名,你能夠在函數中使用這個數組。

省略號也用在帶有其餘參數的函數類型:

function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}

var buildNameFun: (fname: string, ...rest: string[])=>string = buildName;

Lambdas和使用"this"

對於JavaScript程序員來講,關於"this"工做機制的話題算是老生常談了。確實,學會如何使用"this"是JavaScript程序員成長中必須經歷的。由於TypeScript是JavaScript的一個超集,TypeScript程序員也須要學會如何讓使用"this"而且如何處理錯誤使用"this"引起的問題。假如要用來描述如何使用JavaScript中的"this",一整篇文章均可以寫,而且該類文章也有不少。在這裏只介紹基礎知識。

在JavaScript中,"this"在函數被調用的時候被指定。這使得它強大而又靈活,只是你須要爲理解函數執行的上下文付出代價。衆所周知,這是很麻煩的,好比,當一個函數看成回調函數使用的時候。

來看一個例子:

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

var cardPicker = deck.createCardPicker();
var pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

試着運行這個例子,咱們會獲得一個錯誤,而不是預期的彈出警告框。由於'createCardPicker'返回的函數裏"this"指向的是Window對象而不是"deck"對象。因此在這裏調用"cardPicker()",將Window對象綁定到了"this"上。(注意:嚴格模式下,this是undefined而不是Window)

咱們能夠在返回函數的時候就綁定正確的"this",這樣,不管後面怎麼調用這個函數,"this"依舊會指向"deck"對象。
爲了解決這問題,咱們可使用lambda語法代替JavaScript函數表達式。它將會在函數建立的時候自動捕獲"this"變量,而不是在被調用的時候處理"this"。

var deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // 注意: 下面這行如今是使用lambda語法, 更早的捕獲'this'
        return () => {
            var pickedCard = Math.floor(Math.random() * 52);
            var pickedSuit = Math.floor(pickedCard / 13);
            
            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

var cardPicker = deck.createCardPicker();
var pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

想要了解關於"this"的更多信息,能夠閱讀Yahuda Katz的 理解JavaScript函數調用和"this"

重載
JavaScript自己就是動態語言。根據傳入參數的不一樣而返回不一樣類型的值在JavaScript函數中用的並很多。

var suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x) {
    // 檢查咱們處理的是什麼類型的參數
    // 若是是數組對象, 則給定deck而且選擇card
    if (typeof x == "object") {
        var pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // 不然只選擇card
    else if (typeof x == "number") {
        var pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

var myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
var pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

var pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

這裏的"pickCard"將會根據用戶傳入的參數不一樣而返回不一樣的數據。如用戶傳入的是表明"deck"的對象,則函數將會選擇一個"card"而且返回。若是用戶已經選擇了"card",咱們則會返回他選擇的是哪一個"card"。可是怎麼在類型系統裏描述這些呢。

解決方案是基於同一個函數提供多個函數類型,就如函數重載列表。這個列表將被編譯器用來解決函數的調用。如今來建立一個重載列表,描述"pickCard"接收的是什麼,返回的是什麼。

var 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 {
    // 檢查咱們處理的是什麼類型的參數
    // 若是是數組對象, 則給定deck而且選擇card
    if (typeof x == "object") {
        var pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // 不然只選擇card
    else if (typeof x == "number") {
        var pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

var myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
var pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

var pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

有了這些改變,重載後的"pickCard"函數會在被調用的時候進行合適的類型檢查。

爲了編譯器選擇正確的類型檢查,它和JavaScript底層運行是類似的。它會查找重載列表,使用第一次定義的函數進行檢查,若是能匹配,則調用當前這個函數。正是由於如此,通常都會將最明確的定義放在前面。

注意,"function pickCard(x: any): any"不是重載列表的一部分。因此它只有2次重載:一個針對的是object,一次針對的是number。調用"pickCard"而且傳入其餘類型的參數將會致使錯誤。

相關文章
相關標籤/搜索