TypeScript手冊翻譯系列5-函數

函數

函數是JavaScript中任意應用程序的基本構件塊。能夠在函數基礎上創建抽象層,模擬類,信息隱藏,模塊。在TypeScript中,雖然已經有類和模塊,但函數函數仍然扮演着如何作事的關鍵角色。TypeScript還在標準JavaScript 函數基礎上增長了一些新的能力,來使得函數更容易使用。
javascript

函數

TypeScript與JavaScript同樣,均可以建立命名函數,也能夠建立匿名函數。這樣容許開發人員根據應用程序選擇最合適的方式,不管是在一個API中創建一列函數仍是創建一個one-off函數來轉向另外一個函數。

先快速看下JavaScript中這兩種方法是什麼樣的:
java

// Named function-命名函數
function add(x, y) {    
    return x+y;
}

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


就像JavaScript中同樣,函數能夠返回變量。當返回變量時就稱‘捕獲’這些變量。理解這是如何工做,以及使用該技術時須要注意哪些事項,雖然超出了本片文章範圍,但要掌握JavaScript和TypeScript語言,就須要對該機制可以有透徹理解。
typescript

var z = 100;

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

函數類型

Typing the function

咱們對前面的例子添加類型:
編程

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

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

咱們對每一個參數添加類型,而後對函數返回值添加類型。TypeScript能夠經過查看返回語句算出返回類型,因此在許多狀況下也能夠選擇不添加返回類型。
數組

Writing the function type

如今咱們已經對函數添加了類型,如今咱們寫出函數的完整類型,來看看函數類型的每一部分。
dom

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

一個函數的類型有相同的兩部分:參數類型和返回類型。當寫下全部函數類型時,這兩部分都須要寫出。參數類型就像一個參數列表同樣,每一個參數有一個名稱和一個類型。名稱只是有助於可讀性。也能夠寫爲:
函數

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

只要函數的參數類型一一對應,就認爲是有效類型,而不用考慮參數名稱是否相同。

第二部分是返回類型。咱們在參數和返回類型之間使用fat arrow (=>)使返回類型更爲清晰。就像前面提到的,這是函數類型所需的一部分,因此函數若是沒有返回值,應當用'void'而不是什麼也不填寫。

注意,只有參數類型和返回類型組成了函數類型。捕獲的變量並不反映在類型中。實際上,捕獲變量是函數‘隱藏狀態’的一部分,不是API的一部分。

學習

Inferring the types

下面例子中,你會注意到若是在等式的一邊有類型而另外一邊沒有類型時,TypeScript編譯器能夠算出類型:
ui

// myAdd has the full function type
// myAdd有完整的函數類型
var myAdd = function(x: number, y: number): number { return x+y; };

// The parameters 'x' and 'y' have the type number
// 參數'x' 與 'y'的類型是number
var myAdd: (baseValue:number, increment:number)=>number = 
    function(x, y) { return x+y; };


這被稱爲'contextual typing'(上下文類型推斷),一種類型推斷形式。這有助於減小程序中須要鍵入的類型。
this

可選參數與缺省參數

與JavaScript不一樣,在TypeScript中認爲函數的每一個參數都是必須的。這並不表示參數取值不是爲'null',而是當編譯器調用函數時將檢查每一個參數的值。編譯器還假定只有這些參數傳遞給函數。簡言之,輸入的函數參數數量必須與函數期待的參數數量相同。

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

var result1 = buildName("Bob");  //error, too few parameters
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right


在JavaScript中,每一個參數都被視爲可選參數,用戶能夠不填充參數,此時這些未填充的參數自動取值爲undefined。在TypeScript中能夠在可選參數旁邊用'?'來實現相同功能。例如但願last name爲可選參數:

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

var result1 = buildName("Bob");  //works correctly now
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right


可選參數必須跟在必選參數後面。假定想要使first name而不是last name爲可選參數,就須要改變函數參數順序,將first name參數放在後面。

在TypeScript中,當用戶沒有對可選參數提供值時能夠事先設置一個值,這些參數也被稱爲缺省參數。之前面例子舉例,設置last name的缺省值爲"Smith"。

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

var result1 = buildName("Bob");  //works correctly now, also
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right


就像可選參數同樣,在參數列表中缺省參數也必須放在必選參數後面。

可選參數和缺省參數都擁有相同的類型:

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


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

有相同的類型 "(firstName: string, lastName?: string)=>string",缺省參數對應的缺省值消失了,只剩下可選參數。

Rest參數

必選參數,可選參數,缺省參數都有同樣相同:一次只涉及一個參數。有時候但願將多個參數歸爲一組,或不清楚函數最終會傳入多少個參數。在JavaScript中,能夠用函數體內可見的可變參數來表示。

在TypeScript中,能夠將這些參數組合成一個變量:

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

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

Rest參數被看作無窮數量個可選參數。用戶能夠不輸入參數,或根據實際狀況輸入N個參數。編譯器將函數中在省略號...後面的參數名稱用於構建一個參數數組,這樣能夠在函數中使用。


省略號...也用在函數rest參數的類型中:

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

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

Lambdas及使用'this'

在JavaScript函數中'this'如何工做是學習JavaScript編程人員常見的問題。事實上,學習如何使用它就像是開發人員對JavaScript愈來愈駕輕就熟的一種成長儀式。因爲TypeScript是JavaScript的一個超集,TypeScript開發人員也須要學習如何使用'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);


若是運行這個例子,會獲得一個錯誤而不是預期的alert box。這是由於函數中用到的'this'是由'createCardPicker'建立的,它被設置爲'window'而不是'deck'對象。當調用'cardPicker()'時就會發生,這裏'this'除了Window之外沒有動態綁定。(備註:在嚴格模式下,this將等於undefined而不是window)。

能夠在函數返回前將函數綁定到正確的'this'變量來修復該問題。這樣不用考慮函數在後面如何使用,就可以看到最初的'deck' 對象。

爲了修復問題,咱們用lambda語法( ()=>{} )而非JavaScript函數表達式來表示函數。這樣當函數建立時就自動捕獲'this'而不是在函數被調用時捕獲:

var 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 () => {            
            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的Understanding JavaScript Function Invocation and 「this」

譯者注:這篇參考文章的核心思想:

fn(...args)等同於fn.call(window [ES5-strict: undefined], ...args)

(function() {})()等同於(function() {}).call(window [ES5-strict: undefined)

重載(Overloads)

JavaScript本質上就是一種動態語言。常常能夠看到一個JavaScript函數能夠基於傳遞參數的形(shape)返回不一樣類型的對象。

var 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") {        
        var pickedCard = Math.floor(Math.random() * x.length);        
        return pickedCard;
    }    
    // Otherwise just let them pick the 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的對象(一副牌),函數就pick the card(從中挑選一張牌);若是用戶選擇一個數字,就告訴用戶選擇的是什麼牌。但類型系統中如何來描述呢?

答案是對同一個函數提供多個函數類型來重載(overloads)。編譯器用這個列表來解析函數調用。下面建立一組重載函數,來描述'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 {    
    // 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") {        
        var pickedCard = Math.floor(Math.random() * x.length);        
        return pickedCard;
    }    
    // Otherwise just let them pick the 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'代碼片斷不是重載列表,這裏只有兩個重載函數:一個函數接受對象,一個函數接受一個數字。調用'pickCard'時傳入其餘類型參數會致使錯誤。

參考資料

[1] http://www.typescriptlang.org/Handbook#functions

[2] TypeScript系列1-簡介及版本新特性, http://my.oschina.net/1pei/blog/493012

[3] TypeScript手冊翻譯系列1-基礎類型, http://my.oschina.net/1pei/blog/493181

[4] TypeScript手冊翻譯系列2-接口, http://my.oschina.net/1pei/blog/493388

[5] TypeScript手冊翻譯系列3-類, http://my.oschina.net/1pei/blog/493539

[6] TypeScript手冊翻譯系列4-模塊, http://my.oschina.net/1pei/blog/495948

相關文章
相關標籤/搜索