TypeScript - 函數詳解

JS中創建函數的兩種方式

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

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

函數類型

定義函數類型

參數類型 x: number
返回值類型 function add(x: number, y: number): number TypeScript可以通過查看return語句來弄清楚返回類型。通常省略不寫。但是如果沒有返回值推薦寫上void而不是留空

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

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

function checkIn (x:string): void{
    console.log('haha');
}

完整函數類型

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

let newAdd: (baseValue: number, increment: number) => number =
function(x: number, y: number): number { return x + y; }; //在TypeScript裏,只要參數類型匹配。就是有效的函數類型。不在乎你參數名是否正確

myAdd(1, 2) //3
newAdd(1, 2) //3

推斷類型

即使你在賦值語句的一邊指定了類型但是另一邊沒有類型的話,TypeScript編譯器會自動識別出類型。這叫做”上下文歸類”

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

// x, y有number類型
let newAdd: (baseValue: number, increment: number) => number =
    function(x, y) { return x + y; };

可選參數,默認參數

在TypeScript中, 每個函數參數都是必須的,編譯器會檢查用戶是否爲每個參數都傳入了值(null和undefined也算)。傳入的參數個數必須跟函數的參數個數保持一致。

區別: 在Javascript中,每個參數都是可傳可不傳的。沒傳值的時候,參數的值就是undefined

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

let result1 = buildName("Bob");                  // erro 少入要求的參數個數
let result2 = buildName("Bob", "Adams", "Sr.");  // error 超處要求的參數個數
let result3 = buildName("Bob", "Adams"); // "Bob Adams"

可選參數
寫法:參數名?:類型
注:可選參數必須跟在必須參數後面。

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
  }
  
  let result1 = buildName("Bob"); // "Bob"
  let result2 = buildName("Bob", "Adams", "Sr.");  // error 超處要求的參數個數
  let result3 = buildName("Bob", "Adams");  // "Bob Adams" 

默認參數

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

let result1 = buildName("Bob");                  // "Bob Smith"
let result2 = buildName("Bob", undefined);       // "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr.");  // error 超處要求的參數個數
let result4 = buildName("Bob", "Adams");         // "Bob Adams"

實際上在必須參數後定義默認參數跟定義可選參數是共享同樣的類型

function buildName(firstName: string, lastName?: string) {
  // ...
}

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

都是共享的同樣類型

(firstName: string, lastName?: string) => string

默認參數可以任意放置位置,但是注意如果默認值的參數出現在必須參數前面,就必須要明確的傳入 undefined才能獲取默認值。

function buildName(firstName= 'Bob', lastName: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
  }
  
  let result1 = buildName("Bob"); // error 少入要求的參數個數
  let result2 = buildName("Bob", "Adams", "Sr.");  // error 超處要求的參數個數
  let result3 = buildName(undefined, "Adams");  // "Bob Adams"

剩餘參數

利用擴展運算符,在不確定有多少個參數的時候使用

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

buildName("Joseph", "Samuel", "Lucas", "MacKinzie"); //"Joseph Samuel Lucas MacKinzie"
function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

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

buildNameFun("Joseph", "Samuel", "Lucas", "MacKinzie") // "Joseph Samuel Lucas MacKinzie"

this

如果你想了解JavaScript裏的this是如何工作的,那麼首先閱讀Yehuda Katz寫的Understanding JavaScript Function Invocation and “this”

在JavaScript,this是一個當函數被調用時設置的變量。This讓函數變成一個強大且變通的功能。但帶來的成本是你得弄清楚當一個函數被執行時的上下文。衆所周知,這很令人苦惱,尤其是在返回函數或將函數作爲參數傳遞時。(P.s非常贊同這段話,一起來複習一下這甜蜜的煩惱吧)

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 }; //Cannot read property 'suits' of undefined
//調用時候: 非嚴格模式, this其實是window. 嚴格模式時, this是undefined
        };
    }
};

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

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

解決方案
使用箭頭函數綁定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);

this參數

在上述例子中, this.suits[pickedSuit]的類型是any. 因爲this來自於函數. 要解決這個,需要提供一個明確的this 參數。 this是僞參數來自於函數參數列表的第一個

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

回調裏面的this參數

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void; //this: void 意味着addClickListener希望傳進來的onclick是一個方法,並且沒有調用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();
let uiElement: UIElement;
uiElement.addClickListener(h.onClickBad); //Error!!! 傳進去的this是Handler類型。 addClickListener要求this是void

修復上面的error,需要把this: Handler 改成 this:void. 並且方法體內部也不能調用this.

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void; //this: void 意味着addClickListener希望傳進來的onclick是一個方法,並且沒有調用this
}

class Handler {
    info: string;
    onClickBad(this: void, e: Event) {
        // this.info = e.message;
        console.log('clicked');
    }
}
let h = new Handler();
let uiElement: UIElement;
uiElement.addClickListener(h.onClickBad);

如果還是想用this,可以使用箭頭函數。

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void; //this: void 意味着addClickListener希望傳進來的onclick是一個方法,並且沒有調用this
}

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

let h = new Handler();
let uiElement: UIElement;
uiElement.addClickListener(h.onClickBad);

重載(Overload)

JavaScript是一個非常動態的語言,在JavaScript裏函數根據傳入不同的參數而返回不同類型的數據是很常見的。

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

function pickCard(x: any): 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); // card: 4 of hearts

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit); // card: 2 of spades

如果想爲重載做類型檢查,如何做呢?

需要從最具體到最不具體的類型檢查對重載進行排序,編譯器遵循與基礎javaScript 相似的過程,它會查看重載列表,並在第一個重載之前嘗試使用提供的參數調用該函數。如果找到匹配項,它將會選擇此重載作爲正確的重載。

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): 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(x: any) 任何塊不屬於重載列表。只有1和2是重載。一個接收 對象,一個接收數字

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章