TypeScript 文档学习

TypeScript 05 - 函数

2019-11-28  本文已影响0人  晓风残月1994

函数是一等公民,与数值、字符串和数组等地位相等,可以被命名、赋值,可以作为参数传递给另一个函数,也可以作为另一个函数的返回值。在 TS 中,函数依然是主要的定义“行为”的地方,只是 TS 添加了额外功能。

  1. 函数类型
  2. 可选参数和默认参数
  3. 剩余参数
  4. this
  5. 重载

1. 函数类型

函数类型包含两部分:参数类型和返回值类型。一个匿名函数表达式的完整函数类型如下:

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

写出完整的函数类型太麻烦了,其实可以省略等号左边或者右边的类型,TS 会自动地进行类型推断。

2. 可选参数和默认参数

在TypeScript里我们可以在参数名旁使用 ? 实现可选参数的功能,可选参数必须跟在必须参数后面:

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

而默认参数包含了可选参数,可传可不传,传了就要是默认值那种类型:

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

至于默认参数的位置,可以不像可选参数那样放在最后面,但如果默认参数放在前面,当调用函数时即使采用默认参数策略,为了保证参数列表的顺序对应,也要明确传递一个 undefined 占位:

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

let result = buildName(undefined, 'Li');  // Bruce Li

3. 剩余参数

剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。

function buildName(firstName: string, ...restOfName: string[]) {
  console.log(restOfName); // 一个剩余参数都没传递时,restOfName 是 []
  return firstName + ' ' + restOfName.join(" ");
}

let studentName = buildName('Nicholas', 'Super', 'peng');

剩余参数的省略号也可以用在带有剩余参数的函数类型定义上:

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

4. this

this 和 JS 中的一样,判断 this 需要找到函数的调用位置,简单来说大概有四种:
new 绑定 > 显示绑定(callapplybind) > 隐式绑定(上下文对象中调用) > 默认绑定(严格模式下绑定到 undefined,非严格模式下绑定到全局对象,如 window 对象)。

ES6 箭头函数能保存函数创建时的 this 值,而不是调用时的值,即由当前所在的词法作用域决定。

下例中,为了解决 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 的类型是 any,修复方法是添加两个接口,并提供显示的 this 参数。

interface Card {
  suit: string;
  card: number;
}

interface Deck {
  suits: string[];
  cards: string[];
  createCardPicker(this: Deck): () => Card;
}

let deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  createCardPicker: function(this: Deck) {
      // 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();

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

回调函数中的 this

有时候在回调函数依赖 this 是可能报错的,因为将回调函数作为参数传给某库函数被调用时,调用环境发生了变化。库函数作者可能不希望用户提供的回调函数中依赖 this,所以可能会这样指定 this 类型:

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

作为用户,需要提供匹配的参数类型:

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

但如果既要满足接口约定,又想访问当前的 this,则不得不使用箭头函数(箭头函数的参数压根不能有 this 参数,所以也不用写 this: void 了):

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

5. 重载

TS 的重载和 Java 不同,TS 的设计原则之一就是不把类型检查带到runtime,所以编译好后的纯 JS 代码就不存在 TS 那一整套了。TS 的重载机制更主要是为了函数在调用的地方能进行正确的类型检查(在真正运行编译后的 JS 之前)。

在定义重载的时候,一定要把最精确的定义放在最前面。

写法如下,需要注意下面的 pickCard 函数只有两个重载(即只有两种调用传参方式),function pickCard(x): any 并不是重载列表的一部分,那是重载函数的实现:

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);
上一篇 下一篇

猜你喜欢

热点阅读