typescript 函數(shù)

日期: 2019 年 9 月 3 日

typescript 函數(shù)

具名函數(shù)與匿名函數(shù)

和JavaScript一樣,TypeScript函數(shù)可以創(chuàng)建有名字的函數(shù)和匿名函數(shù)

/**
* @description Named function
* @param {*} x
* @param {*} y
* @returns
*/
function add(x, y){
    return x + y;
}

/**
* Anonymous function
* @param x
* @param y
*/
let myAdd = function(x, y){
    return x + y;
}

在JavaScript里,函數(shù)可以使用函數(shù)體外部的變量。 當函數(shù)這么做時,我們說它‘捕獲’了這些變量


/**
* 捕獲——函數(shù)使用函數(shù)體外部的變量
*/
let z = 100;
function addToZ(x, y){
    return x + y + z;
}

函數(shù)類型

為函數(shù)定義類型

讓我們?yōu)樯厦婺莻€函數(shù)添加類型:


/**
* @description 為函數(shù)定義類型
* @param {number} x
* @param {number} y
* @returns {number}
*/
function add1(x: number, y: number): number{
    return x + y;
}
let myAdd1 = function(x: number, y: number): number{ return x + y };

完整的函數(shù)類型

現(xiàn)在我們已經(jīng)為函數(shù)指定了類型,下面讓我們寫出函數(shù)的完整類型

/**
* 書寫完整的函數(shù)類型
* @param x
* @param y
*/
let myAdd2: (x: number, y: number) => number =
    function(x: number, y: number): number{ return x + y; }

函數(shù)類型包含兩部分:參數(shù)類型和返回值類型。 當寫出完整函數(shù)類型的時候,這兩部分都是需要的。 我們以參數(shù)列表的形式寫出參數(shù)類型,為每個參數(shù)指定一個名字和類型。 這個名字只是為了增加可讀性。 我們也可以這么寫:

/**
* 函數(shù)類型包含兩部分:參數(shù)類型和返回值類型,
* 只要參數(shù)類型是匹配的,那么就認為它是有效的函數(shù)類型,而不在乎參數(shù)名是否正確
* @param x
* @param y
*/
let myAdd3: (baseValue: number, increment: number) => number =
    function(x: number, y: number): number{ return x + y; }

只要參數(shù)類型是匹配的,那么就認為它是有效的函數(shù)類型,而不在乎參數(shù)名是否正確

推斷類型

如果你在賦值語句的一邊指定了類型但是另一邊沒有類型的話,TypeScript編譯器會自動識別出類型:


**
* 推斷類型
* @param x
* @param y
*/
let myAdd4: (baseValue: number, increment: number) => number =
    function(x, y){ return x + y; }

可選參數(shù)與默認參數(shù)

可選參數(shù)

TypeScript里的每個函數(shù)參數(shù)都是必須的。 這不是指不能傳遞 null或undefined作為參數(shù),而是說編譯器檢查用戶是否為每個參數(shù)都傳入了值。 編譯器還會假設只有這些參數(shù)會被傳遞進函數(shù)。 簡短地說,傳遞給一個函數(shù)的參數(shù)個數(shù)必須與函數(shù)期望的參數(shù)個數(shù)一致

/**
* TypeScript里的每個函數(shù)參數(shù)都是必須的
* 傳遞給一個函數(shù)的參數(shù)個數(shù)必須與函數(shù)期望的參數(shù)個數(shù)一致
* @param firstName
* @param lastName
*/
function buildName( firstName: string, lastName: string ){
    return firstName + " " + lastName;
}
// let result1 = buildName("Bob"); // error
// let result2 = buildName("Bob", "Smith", "Sr."); // error
let result3 = buildName("zhang", "li"); // ok!

JavaScript里,每個參數(shù)都是可選的,可傳可不傳。 沒傳參的時候,它的值就是undefined。 在TypeScript里我們可以在參數(shù)名旁使用 ?實現(xiàn)可選參數(shù)的功能。 比如,我們想讓last name是可選的:

/**
* JavaScript里,每個參數(shù)都是可選的,可傳可不傳
* 在TypeScript里我們可以在參數(shù)名旁使用 ?實現(xiàn)可選參數(shù)的功能, 比如,我們想讓last name是可選的
* 可選參數(shù)必須跟在必須參數(shù)后面,如果我們想讓first name是可選的,那么就必須調整它們的位置,把first name放在后面
* @param firstName
* @param lastName
*/
function buildName1( firstName: string, lastName?: string ){
    if(lastName){
        return firstName + " " + lastName;
    }else{
        return firstName;
    }
}
let result1 = buildName1("Bob"); // ok
// let result2 = buildName1("Bob", "Smith", "sr."); // error
let result4 = buildName1("zhang", "li"); // ok

可選參數(shù)必須跟在必須參數(shù)后面。 如果上例我們想讓first name是可選的,那么就必須調整它們的位置,把first name放在后面

默認參數(shù)

在TypeScript里,我們也可以為參數(shù)提供一個默認值當用戶沒有傳遞這個參數(shù)或傳遞的值是undefined時。 它們叫做有默認初始化值的參數(shù)。 讓我們修改上例,把last name的默認值設置為"Smith"


/**
* 在TypeScript里,我們也可以為參數(shù)提供一個默認值當用戶沒有傳遞這個參數(shù)或傳遞的值是undefined時
* 它們叫做有默認初始化值的參數(shù)
* @param firstName
* @param lastName
*/
function buildName2(firstName: string, lastName = "Smith" ){
    return firstName + " " + lastName;
}
let result2 = buildName2("Bob"); // ok "Bob Smith"
let result5 = buildName2("Bob",undefined); // ok "Bob Smith"
// let result6 = buildName2("Bob","Adama", "Sr."); // error
let result6 = buildName2( "zhang", "li"); // ok "zhang li"

與普通可選參數(shù)不同的是,帶默認值的參數(shù)不需要放在必須參數(shù)的后面

/**
* 與普通可選參數(shù)不同的是,帶默認值的參數(shù)不需要放在必須參數(shù)的后面
* 如果帶默認值的參數(shù)出現(xiàn)在必須參數(shù)前面,用戶必須明確的傳入 undefined值來獲得默認值
* @param firstName
* @param lastName
*/
function buildName3(firstName = "will", lastName: string){
    return firstName + " " + lastName;
}
// let result7 = buildName3("Bob"); // error
let result8 = buildName3("zhang", "li"); // ok "zhang li"
let result9 = buildName3(undefined, "Smith"); // ok "will Smith"

剩余參數(shù)

必要參數(shù),默認參數(shù)和可選參數(shù)有個共同點:它們表示某一個參數(shù)。 有時,你想同時操作多個參數(shù),或者你并不知道會有多少參數(shù)傳遞進來。 在JavaScript里,你可以使用 arguments來訪問所有傳入的參數(shù)

在TypeScript里,你可以把所有參數(shù)收集到一個變量里:

/**
* 剩余參數(shù)
* 剩余參數(shù)會被當做個數(shù)不限的可選參數(shù),可以一個都沒有,同樣也可以有任意個
* @param firstName
* @param restOfName
*/
function buildName4(firstName: string, ...restOfName: string[]){
    return firstName + " " + restOfName.join("-");
}
let employeeNames = buildName4("Bob", "Will", "Smith", "Haha", "Heihei");
console.log(employeeNames);
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName4;
let employeeName = buildName4("Bob", "Will", "Smith", "Haha", "Heihei");
console.log(employeeName);

this

this 和箭頭函數(shù)

JavaScript里,this的值在函數(shù)被調用的時候才會指定。 這是個既強大又靈活的特點,但是你需要花點時間弄清楚函數(shù)調用的上下文是什么。 但眾所周知,這不是一件很簡單的事,尤其是在返回一個函數(shù)或將函數(shù)當做參數(shù)傳遞的時候。

下面看一個例子:

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);

可以看到createCardPicker是個函數(shù),并且它又返回了一個函數(shù)。 如果我們嘗試運行這個程序,會發(fā)現(xiàn)它并沒有彈出對話框而是報錯了。 因為 createCardPicker返回的函數(shù)里的this被設置成了window而不是deck對象。 因為我們只是獨立的調用了 cardPicker()。 頂級的非方法式調用會將 this視為window。 (注意:在嚴格模式下, this為undefined而不是window)。

為了解決這個問題,我們可以在函數(shù)被返回時就綁好正確的this。 這樣的話,無論之后怎么使用它,都會引用綁定的‘deck’對象。 我們需要改變函數(shù)表達式來使用ECMAScript 6箭頭語法。 箭頭函數(shù)能保存函數(shù)創(chuàng)建時的 this值,而不是調用時的值:

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
        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);

更好事情是,TypeScript會警告你犯了一個錯誤,如果你給編譯器設置了--noImplicitThis標記。 它會指出 this.suits[pickedSuit]里的this的類型為any

this 參數(shù)

不幸的是,this.suits[pickedSuit]的類型依舊為any。 這是因為 this來自對象字面量里的函數(shù)表達式。 修改的方法是,提供一個顯式的 this參數(shù)。 this參數(shù)是個假的參數(shù),它出現(xiàn)在參數(shù)列表的最前面:

function f(this: void) {
    // make sure `this` is unusable in this standalone function
}

讓我們往例子里添加一些接口,Card 和 Deck,讓類型重用能夠變得清晰簡單些:

interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // NOTE: The function now explicitly specifies that its callee must be of type Deck
    createCardPicker: function(this: Deck) {
        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);

現(xiàn)在TypeScript知道createCardPicker期望在某個Deck對象上調用。 也就是說 this是Deck類型的,而非any,因此--noImplicitThis不會報錯了

在回調中使用 this

你可以也看到過在回調函數(shù)里的this報錯,當你將一個函數(shù)傳遞到某個庫函數(shù)里稍后會被調用時。 因為當回調被調用的時候,它們會被當成一個普通函數(shù)調用, this將為undefined。 稍做改動,你就可以通過 this參數(shù)來避免錯誤。 首先,庫函數(shù)的作者要指定 this的類型:

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}

this: void means that addClickListener expects onclick to be a function that does not require a this type. Second, annotate your calling code with this:

class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // oops, used this here. using this callback would crash at runtime
        this.info = e.message;
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!

指定了this類型后,你顯式聲明onClickBad必須在Handler的實例上調用。 然后TypeScript會檢測到 addClickListener要求函數(shù)帶有this: void。 改變 this類型來修復這個錯誤:

class Handler {
    info: string;
    onClickGood(this: void, e: Event) {
        // can't use this here because it's of type void!
        console.log('clicked!');
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);

因為onClickGood指定了this類型為void,因此傳遞addClickListener是合法的。 當然了,這也意味著不能使用 this.info. 如果你兩者都想要,你不得不使用箭頭函數(shù)了:

class Handler {
    info: string;
    onClickGood = (e: Event) => { this.info = e.message }
}

這是可行的因為箭頭函數(shù)不會捕獲this,所以你總是可以把它們傳給期望this: void的函數(shù)。 缺點是每個 Handler對象都會創(chuàng)建一個箭頭函數(shù)。 另一方面,方法只會被創(chuàng)建一次,添加到 Handler的原型鏈上。 它們在不同 Handler對象間是共享的。

重載

JavaScript本身是個動態(tài)語言。 JavaScript里函數(shù)根據(jù)傳入不同的參數(shù)而返回不同類型的數(shù)據(jù)是很常見的。

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ù)的不同會返回兩種不同的類型。 如果傳入的是代表紙牌的對象,函數(shù)作用是從中抓一張牌。 如果用戶想抓牌,我們告訴他抓到了什么牌。 但是這怎么在類型系統(tǒng)里表示呢。


方法是為同一個函數(shù)提供多個函數(shù)類型定義來進行函數(shù)重載。 編譯器會根據(jù)這個列表去處理函數(shù)的調用。 下面我們來重載 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ù)在調用的時候會進行正確的類型檢查。


為了讓編譯器能夠選擇正確的檢查類型,它與JavaScript里的處理流程相似。 它查找重載列表,嘗試使用第一個重載定義。 如果匹配的話就使用這個。 因此,在定義重載的時候,一定要把最精確的定義放在最前面。


注意,function pickCard(x): any并不是重載列表的一部分,因此這里只有兩個重載:一個是接收對象另一個接收數(shù)字。 以其它參數(shù)調用 pickCard會產(chǎn)生錯誤

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容