函數是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; }
咱們對前面的例子添加類型:
編程
function add(x: number, y: number): number { return x+y; } var myAdd = function(x: number, y: number): number { return x+y; };
咱們對每一個參數添加類型,而後對函數返回值添加類型。TypeScript能夠經過查看返回語句算出返回類型,因此在許多狀況下也能夠選擇不添加返回類型。
數組
如今咱們已經對函數添加了類型,如今咱們寫出函數的完整類型,來看看函數類型的每一部分。
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的一部分。
學習
下面例子中,你會注意到若是在等式的一邊有類型而另外一邊沒有類型時,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",缺省參數對應的缺省值消失了,只剩下可選參數。
必選參數,可選參數,缺省參數都有同樣相同:一次只涉及一個參數。有時候但願將多個參數歸爲一組,或不清楚函數最終會傳入多少個參數。在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;
在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)
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