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

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

1. 函数类型

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

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

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

2. 可选参数和默认参数

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

  1. function buildName(firstName: string, lastName?: string) {
  2. if (lastName)
  3. return firstName + " " + lastName;
  4. else
  5. return firstName;
  6. }

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

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

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

  1. function buildName(firstName = "Bruce", lastName: string) {
  2. return firstName + " " + lastName;
  3. }
  4. let result = buildName(undefined, 'Li'); // Bruce Li

3. 剩余参数

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

  1. function buildName(firstName: string, ...restOfName: string[]) {
  2. console.log(restOfName); // 一个剩余参数都没传递时,restOfName 是 []
  3. return firstName + ' ' + restOfName.join(" ");
  4. }
  5. let studentName = buildName('Nicholas', 'Super', 'peng');

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

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

4. this

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

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

下例中,为了解决 this 不会进行默认绑定到全局对象 ,所以使用了箭头函数:

  1. let deck = {
  2. suits: ["hearts", "spades", "clubs", "diamonds"],
  3. cards: Array(52),
  4. createCardPicker: function() {
  5. // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
  6. return () => {
  7. let pickedCard = Math.floor(Math.random() * 52);
  8. let pickedSuit = Math.floor(pickedCard / 13);
  9. return {suit: this.suits[pickedSuit], card: pickedCard % 13};
  10. }
  11. }
  12. }
  13. let cardPicker = deck.createCardPicker();
  14. let pickedCard = cardPicker();
  15. alert("card: " + pickedCard.card + " of " + pickedCard.suit);

但不幸是,this 的类型是 any,修复方法是添加两个接口,并提供显示的 this 参数。

  1. interface Card {
  2. suit: string;
  3. card: number;
  4. }
  5. interface Deck {
  6. suits: string[];
  7. cards: string[];
  8. createCardPicker(this: Deck): () => Card;
  9. }
  10. let deck = {
  11. suits: ["hearts", "spades", "clubs", "diamonds"],
  12. cards: Array(52),
  13. createCardPicker: function(this: Deck) {
  14. // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
  15. return () => {
  16. let pickedCard = Math.floor(Math.random() * 52);
  17. let pickedSuit = Math.floor(pickedCard / 13);
  18. return {suit: this.suits[pickedSuit], card: pickedCard % 13};
  19. }
  20. }
  21. }
  22. let cardPicker = deck.createCardPicker();
  23. let pickedCard = cardPicker();
  24. console.log("card: " + pickedCard.card + " of " + pickedCard.suit);

回调函数中的 this

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

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

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

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

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

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

5. 重载

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

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

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

  1. let suits = ["hearts", "spades", "clubs", "diamonds"];
  2. function pickCard(x: {suit: string; card: number; }[]): number;
  3. function pickCard(x: number): {suit: string; card: number; };
  4. function pickCard(x): any {
  5. // Check to see if we're working with an object/array
  6. // if so, they gave us the deck and we'll pick the card
  7. if (typeof x == "object") {
  8. let pickedCard = Math.floor(Math.random() * x.length);
  9. return pickedCard;
  10. }
  11. // Otherwise just let them pick the card
  12. else if (typeof x == "number") {
  13. let pickedSuit = Math.floor(x / 13);
  14. return { suit: suits[pickedSuit], card: x % 13 };
  15. }
  16. }
  17. let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
  18. let pickedCard1 = myDeck[pickCard(myDeck)];
  19. alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
  20. let pickedCard2 = pickCard(15);
  21. alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);