typescript初探之函數(function)與泛型(generic)

之前我們有簡單瞭解了一下使用接口表示函數類型。接下來我們瞭解 typescript 中的函數和泛型。

函數
//聲明式
function add(x, y) {
  return x + y;
}
// 函數表達式
let myAdd = function(x, y) {
  return x + y;
};

JavaScript 聲明方式有聲明式和表達式,另外有具名函數和匿名函數:

// 匿名函數
(function() {
  let a = 20;
  console.log(a);
})();

typescript 除了在 JavaScript 基礎上添加了一系列校驗外其它沒什麼很大區別。
typescript 函數類型:

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

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

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

// 函數的參數名稱不需要與參數類型定義時的名稱一致
let myAdd: (baseValue: number, increment: number) => number = function(
  x: number,
  y: number
): number {
  return x + y;
};

這裏我們隊函數的參數和返回值指定了 number 類型的校驗。TypeScript 能夠根據返回語句自動推斷出返回值類型,因此我們可以省略它。完整函數類型等於號之前的分別爲參數和返回值的類型校驗,有點類似寫箭頭函數()=>,=>箭頭代表返回。函數的參數名稱不需要和參數類型定義時得名稱一致。

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

// 當指定了參數類型定義但 待校驗函數參數沒有標註類型時,typescript會自動識別類型
let myAdd: (baseValue: number, increment: number) => number = function(x, y) {
  return x + y;
};

當指定了參數類型定義但 待校驗函數參數沒有標註類型時,typescript 會自動識別類型,按照類型定義規則校驗。
JavaScript 裏函數的參數是可選的定義之後可傳可不傳,沒有傳參的時候是 undefined,例如:

function aa(name, age) {
  // do something
}

但是 typescript 裏一旦表明這個函數有參數,那麼在調用這個函數的時候必須加上參數。如果我們想要添加可選參數,可以使用可選類型:

// 可選參數必須跟在必須參數後面。 如果我們想讓first name是可選的,那麼就必須調整它們的位置,把first name放在後面
function buildName(firstName: string, lastName?: string) {
  if (lastName){
      return firstName + " " + lastName;
  } else{
      else return firstName;
  }
}

我們也可以直接像 ES6 一樣給參數一個默認值:

// 當調用函數沒有傳參數lastName時,lastName的默認值爲Smith
function buildName(firstName: string, lastName = "Smith") {
  return firstName + " " + lastName;
}

與普通可選參數不同的是,帶默認值的參數不需要放在必須參數的後面。 如果帶默認值的參數出現在必須參數前面,用戶必須明確的傳入undefined值來獲得默認值——————這裏其實意思是(如下面栗子)當我們將默認參數寫在必需參數的前面,調用函數只傳了一個參數那麼這個參數對應的是 firstName,而 lastName 是必需的相當於沒有 lastName 的參數,所以報錯。所以如果想要Bob對應 lastName 的值,就需要在第一個參數的位置傳入undefined

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

let result1 = buildName("Bob"); //An argument for 'lastName' was not provided.
let result4 = buildName(undefined, "Bob"); // "Will Bob"

對於不知道會有多少個參數的函數,我們也可以像 ES6 一樣添加使用擴展符表示:

// 使用... 擴展符,表示可展開的數據
function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

在 JavaScript 中,this 的指向與函數被調用的上下文有關係,這使得我們不太好區分。例如:

let deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  suits: 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);

這個例子會報錯,我們來解析一下,deck是一個對象,他包含有 suits、suits 屬性和 createCardPicker 方法。createCardPicker方法返回了一個匿名函數。let cardPicker = deck.createCardPicker()這句意思是拿到createCardPicker的返回函數,let pickedCard = cardPicker();這句的意思是執行匿名函數並且拿到該函數的返回對象。
前面提到了在 JavaScript 中,this 的指向與函數被調用的上下文有關係,在上面的例子中,調用匿名函數的是 window/global,相當於以下函數:

function cardPicker() {
  let pickedCard = Math.floor(Math.random() * 52);
  let pickedSuit = Math.floor(pickedCard / 13);
  return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
}

cardPicker();

在 window/global 中找不到suits對象,所以報錯。
這時,JavaScript 提供了一系列改變函數執行上下文的方法:call、apply、bind。我們就可以修改上述例子:

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.call(deck); //手動將cardPicker函數(也就是createCardPicker返回的匿名函數)中的this指向deck對象。

console.log("card: " + pickedCard.card + " of " + pickedCard.suit); // card: 7 of diamonds

也可以在定義這個函數的時候就綁定該函數環境的 this:

let deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  createCardPicker: function() {
    let that = this; // 在這裏使用that變量保存 this
    return function() {
      let pickedCard = Math.floor(Math.random() * 52);
      let pickedSuit = Math.floor(pickedCard / 13);
      return { suit: that.suits[pickedSuit], card: pickedCard % 13 };
    };
  }
};

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit); // card: 12 of spades

對於函數 this 指向問題,ES6 提供了箭頭函數,能夠能保存函數創建時的 this,被調用的時候不會改變 this 指向:

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

在使用 typescript 的時候,this 默認爲 any 類型,如果想要規定 this 的類型,可以寫成如下:

// Card接口
interface Card {
  suit: string;
  card: number;
}
// Deck接口
interface Deck {
  suits: string[];
  cards: number[];
  // 函數類型接口
  createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  // createCardPicker函數 this類型爲Deck,返回值類型爲Card
  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 };
    };
  }
};
泛型

平常我們不使用 typescript 進行類型判斷的時候,函數的返回值類型與我們傳給函數的參數類型不一定相同,可能會因爲我們不合理的操作導致返回值類型不是傳入值類型。
平常寫法,只能傳入是 number,返回是 number,靈活性不好:

function identity(arg: number): number {
  return arg;
}

使用 any,any 代表接收任意類型,那和不設置 typescript 差不多,傳入參數類型值和返回類型值不一定相同:

function identity(arg: any): any {
  return arg;
}

爲了使返回值的類型與傳入參數的類型相同,typescript 可以使用類型變量來達到目的:

// 泛型函數
function identity<T>(arg: T): T {
  return arg;
}

類型名稱可以不相同:

function identity<T>(arg: T): T {
  return arg;
}
let myIdentity: <U>(arg: U) => U = identity;

此時 T 代表可以是任意類型的數據,但因爲 T 可以是任意類型數據,所以得注意如果使用了一些特定類型纔有的屬性,就會報錯:

function loggingIdentity<T>(arg: T): T {
  console.log(arg.length); // error,由於T可以是任意類型數據,有些類型是沒有長度屬性的,比如number
  return arg;
}

數組:
這樣傳入時數組爲任意類型,返回時爲傳入時的類型,且數組有 length 屬性

function foo<T>(arg: T[]): T[] {
  console.log(arg.length);
  return arg;
}

也可以寫成:

function foo<T>(arg: Array<T>): Array<T> {
  return arg;
}

類型是一個對象字面量:

function identity<T>(arg: T): T {
  return arg;
}
let myIdentity: { <U>(arg: U): U } = identity;

泛型接口,和普通接口寫起來差不多,只是將類型換成了類型變量:

interface GenericIdentityFn {
  <U>(arg: U): U;
}
function identity<T>(arg: T): T {
  return arg;
}
let myIdentity: GenericIdentityFn = identity;

泛型類,和直接寫類差不多,類名稱後面加個變量類型,其他類型也換成變量類型:

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>(); // 明確GenericNumber接口類型爲number
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章