### 彪哥

爱好前端

- 初识 TypeScript

- TypeScript 常用语法

  - 基础类型
  - 变量声明
  - 接口
  - 类
  - 函数
    - 基本示例
    - 函数类型
      - 为函数定义类型
      - 书写完整函数类型
      - 推断类型
    - 可选参数和默认参数
      - 剩余参数
    - this
      - this 和箭头函数
      - this 参数
      - this 参数在回调函数里
    - 重载
  - 泛型
  - 类型推断
  - 高级类型

- ts-axios 项目初始化

- ts-axios 基础功能实现

- ts-axios 异常情况处理

- ts-axios 接口扩展

- ts-axios 拦截器实现

- ts-axios 配置化实现

- ts-axios 取消功能实现

- ts-axios 更多功能实现

- ts-axios 单元测试

- ts-axios 部署与发布

- 《TypeScript 从零实现 axios》
- TypeScript 常用语法

HuangYi
2020-01-05
目录

# 函数

# # 函数

函数是 JavaScript 应用程序的基础，它帮助你实现抽象层，模拟类，信息隐藏和模块。在 TypeScript 里，虽然已经支持类，命名空间和模块，但函数仍然是主要的定义行为的地方。TypeScript 为 JavaScript 函数添加了额外的功能，让我们可以更容易地使用。

## # 基本示例

和 JavaScript 一样，TypeScript 函数可以创建有名字的函数和匿名函数。你可以随意选择适合应用程序的方式，不论是定义一系列 API 函数还是只使用一次的函数。

通过下面的例子可以迅速回想起这两种 JavaScript 中的函数：

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

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

1
2
3
4
5
6
7
8
9

在 JavaScript 里，函数可以使用函数体外部的变量。 当函数这么做时，我们说它‘捕获’了这些变量。 至于为什么可以这样做以及其中的利弊超出了本文的范围，但是深刻理解这个机制对学习 JavaScript 和 TypeScript 会很有帮助。

    let z = 100

    function addToZ(x, y) {
      return x + y + z
    }

1
2
3
4
5

## # 函数类型

### # 为函数定义类型

让我们为上面那个函数添加类型：

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

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

1
2
3
4
5
6
7

我们可以给每个参数添加类型之后再为函数本身添加返回值类型。TypeScript 能够根据返回语句自动推断出返回值类型。

### # 书写完整函数类型

现在我们已经为函数指定了类型，下面让我们写出函数的完整类型。

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

1
2
3
4
5

函数类型包含两部分：参数类型和返回值类型。 当写出完整函数类型的时候，这两部分都是需要的。 我们以参数列表的形式写出参数类型，为每个参数指定一个名字和类型。这个名字只是为了增加可读性。 我们也可以这么写：

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

1
2
3
4

只要参数类型是匹配的，那么就认为它是有效的函数类型，而不在乎参数名是否正确。

第二部分是返回值类型。 对于返回值，我们在函数和返回值类型之前使用(`=>`)符号，使之清晰明了。 如之前提到的，返回值类型是函数类型的必要部分，如果函数没有返回任何值，你也必须指定返回值类型为 `void` 而不能留空。

函数的类型只是由参数类型和返回值组成的。 函数中使用的捕获变量不会体现在类型里。 实际上，这些变量是函数的隐藏状态并不是组成 API 的一部分。

### # 推断类型

尝试这个例子的时候，你会发现如果你在赋值语句的一边指定了类型但是另一边没有类型的话，TypeScript 编译器会自动识别出类型：

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

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

1
2
3
4
5
6
7
8

这叫做“按上下文归类”，是类型推论的一种。它帮助我们更好地为程序指定类型。

## # 可选参数和默认参数

TypeScript 里的每个函数参数都是必须的。 这不是指不能传递 `null` 或 `undefined` 作为参数，而是说编译器检查用户是否为每个参数都传入了值。编译器还会假设只有这些参数会被传递进函数。 简短地说，传递给一个函数的参数个数必须与函数期望的参数个数一致。

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

    let result1 = buildName('Bob')                  // Error, 参数过少
    let result2 = buildName('Bob', 'Adams', 'Sr.');  // Error, 参数过多
    let result3 = buildName('Bob', 'Adams');         // OK

1
2
3
4
5
6
7

JavaScript 里，每个参数都是可选的，可传可不传。 没传参的时候，它的值就是 `undefined`。 在TypeScript 里我们可以在参数名旁使用 `?` 实现可选参数的功能。 比如，我们想让 `lastName` 是可选的：

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

    let result1 = buildName('Bob');  // 现在正常了
    let result2 = buildName('Bob', 'Adams', 'Sr.')  // Error, 参数过多
    let result3 = buildName('Bob', 'Adams')  // OK

1
2
3
4
5
6
7
8
9
10

可选参数必须跟在必须参数后面。 如果上例我们想让 `firstName` 是可选的，那么就必须调整它们的位置，把 `firstName` 放在后面。

在 TypeScript 里，我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是 `undefined` 时。 它们叫做有默认初始化值的参数。 让我们修改上例，把`lastName` 的默认值设置为 `"Smith"`。

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

    let result1 = buildName('Bob')                  // 返回 "Bob Smith"
    let result2 = buildName('Bob', undefined)     // 正常, 同样 "Bob Smith"
    let result3 = buildName('Bob', 'Adams', 'Sr.')  // 错误, 参数过多
    let result4 = buildName('Bob', 'Adams')        // OK

1
2
3
4
5
6
7
8

与普通可选参数不同的是，带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面，用户必须明确的传入 `undefined` 值来获得默认值。 例如，我们重写最后一个例子，让 `firstName` 是带默认值的参数：

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

    let result1 = buildName('Bob')                  // Error, 参数过少
    let result2 = buildName('Bob', 'Adams', "Sr.")  // Error, 参数过多
    let result3 = buildName('Bob', 'Adams')         // OK， 返回 "Bob Adams"
    let result4 = buildName(undefined, 'Adams')     // OK，  返回 "Will Adams"

1
2
3
4
5
6
7
8

### # 剩余参数

必要参数，默认参数和可选参数有个共同点：它们表示某一个参数。 有时，你想同时操作多个参数，或者你并不知道会有多少参数传递进来。 在 JavaScript 里，你可以使用 `arguments` 来访问所有传入的参数。

在 TypeScript 里，你可以把所有参数收集到一个变量里：

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

    let employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie')

1
2
3
4
5

剩余参数会被当做个数不限的可选参数。 可以一个都没有，同样也可以有任意个。 编译器创建参数数组，名字是你在省略号（ `...`）后面给定的名字，你可以在函数体内使用这个数组。

这个省略号也会在带有剩余参数的函数类型定义上使用到：

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

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

1
2
3
4
5

## # this

学习如何在 JavaScript 里正确使用 `this` 就好比一场成年礼。由于 TypeScript 是 JavaScript 的超集，TypeScript 程序员也需要弄清 `this` 工作机制并且当有 bug 的时候能够找出错误所在。 幸运的是，TypeScript 能通知你错误地使用了 `this` 的地方。 如果你想了解 JavaScript 里的 this是如何工作的，那么首先阅读 Yehuda Katz 写的 Understanding JavaScript Function Invocation and "this"
(opens new window)。 Yehuda 的文章详细的阐述了 `this` 的内部工作原理，因此我们这里只做简单介绍。

### # this 和箭头函数

JavaScript里，`this` 的值在函数被调用的时候才会指定。 这是个既强大又灵活的特点，但是你需要花点时间弄清楚函数调用的上下文是什么。但众所周知，这不是一件很简单的事，尤其是在返回一个函数或将函数当做参数传递的时候。

下面看一个例子：

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

    console.log('card: ' + pickedCard.card + ' of ' + pickedCard.suit)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

可以看到 `createCardPicker` 是个函数，并且它又返回了一个函数。如果我们尝试运行这个程序，会发现它并没有输出而是报错了。 因为 `createCardPicker` 返回的函数里的 `this` 被设置成了 `global` 而不是 `deck` 对象。 因为我们只是独立的调用了 `cardPicker()`。 顶级的非方法式调用会将 `this` 视为 `global`。

为了解决这个问题，我们可以在函数被返回时就绑好正确的`this`。 这样的话，无论之后怎么使用它，都会引用绑定的`deck` 对象。 我们需要改变函数表达式来使用 ECMAScript 6 箭头语法。 箭头函数能保存函数创建时的 `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}
        }
      }
    }

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

    console.log('card: ' + pickedCard.card + ' of ' + pickedCard.suit)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

### # this 参数

在上述的例子中 `this.suits[pickedSuit]` 的类型为 `any`，这是因为 `this` 来自对象字面量里的函数表达式。 修改的方法是，提供一个显式的 `this` 参数。 `this` 参数是个假的参数，它出现在参数列表的最前面：

    function f(this: void) {
      // 确保“this”在此独立函数中不可用
    }

1
2
3

让我们往例子里添加一些接口，`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: 函数现在显式指定其被调用方必须是 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()

    console.log('card: ' + pickedCard.card + ' of ' + pickedCard.suit)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

现在 TypeScrip t知道 `createCardPicker` 期望在某个 `Deck` 对象上调用。也就是说 `this` 是 `Deck` 类型的，而非 `any`。

### # this 参数在回调函数里

你可以也看到过在回调函数里的 `this` 报错，当你将一个函数传递到某个库函数里稍后会被调用时。 因为当回调被调用的时候，它们会被当成一个普通函数调用，`this` 将为 `undefined`。 稍做改动，你就可以通过 `this` 参数来避免错误。 首先，库函数的作者要指定 `this` 的类型：

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

1
2
3

`this: void` 意味着 `addClickListener` 期望传入的 `onclick` 方法不需要 `this`

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

    class Handler {
      type: string

      onClickBad (this: Handler, e: Event) {
        this.type = e.type
      }
    }

    let h = new Handler()

    let uiElement: UIElement = {
      addClickListener () {
      }
    }

    uiElement.addClickListener(h.onClickBad) // error!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

指定了 `this` 类型后，你显式声明 `onClickBad` 必须在 `Handler` 的实例上调用。 然后 TypeScript 会检测到 `addClickListener` 要求函数带有 `this: void`。 改变 `this` 类型来修复这个错误：

    class Handler {
      type: string;

      onClickBad (this: void, e: Event) {
        console.log('clicked!')
      }
    }

    let h = new Handler()

    let uiElement: UIElement = {
      addClickListener () {
      }
    }

    uiElement.addClickListener(h.onClickBad)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

因为 `onClickGood` 指定了 `this` 类型为 `void`，因此传递 `addClickListener` 是合法的。 当然了，这也意味着不能使用 `this.info`。 如果你两者都想要，你不得不使用箭头函数了：

    class Handler {
      type: string
      onClickGood = (e: Event) => {
        this.type = e.type
      }
    }

1
2
3
4
5
6

这是可行的因为箭头函数不会捕获 `this`，所以你总是可以把它们传给期望 `this: void` 的函数。

## # 重载

JavaScript 本身是个动态语言。JavaScript 里函数根据传入不同的参数而返回不同类型的数据的场景是很常见的。

    let suits = ['hearts', 'spades', 'clubs', 'diamonds']

    function pickCard(x): any {
      if (Array.isArray(x)) {
        let pickedCard = Math.floor(Math.random() * x.length)
        return pickedCard
      } 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)];
    console.log('card: ' + pickedCard1.card + ' of ' + pickedCard1.suit)

    let pickedCard2 = pickCard(15)
    console.log('card: ' + pickedCard2.card + ' of ' + pickedCard2.suit)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

`pickCard` 方法根据传入参数的不同会返回两种不同的类型。如果传入的是代表纸牌的对象数组，函数作用是从中抓一张牌。如果用户想抓牌，我们告诉他抓到了什么牌。 但是这怎么在类型系统里表示呢。

方法是为同一个函数提供多个函数类型定义来进行函数重载。 编译器会根据这个列表去处理函数的调用。 下面我们来重载 `pickCard` 函数。

    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 {
      if (Array.isArray(x)) {
        let pickedCard = Math.floor(Math.random() * x.length)
        return pickedCard
      } 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)];
    console.log('card: ' + pickedCard1.card + ' of ' + pickedCard1.suit)

    let pickedCard2 = pickCard(15)
    console.log('card: ' + pickedCard2.card + ' of ' + pickedCard2.suit)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

这样改变后，重载的 `pickCard` 函数在调用的时候会进行正确的类型检查。

为了让编译器能够选择正确的检查类型，它与 JavaScript 里的处理流程相似。它查找重载列表，尝试使用第一个重载定义。 如果匹配的话就使用这个。因此，在定义重载的时候，一定要把最精确的定义放在最前面。

注意，`function pickCard(x): any` 并不是重载列表的一部分，因此这里只有两个重载：一个是接收对象数组，另一个接收数字。 以其它参数调用 `pickCard` 会产生错误。

编辑
(opens new window)
#TypeScript
上次更新: 2022/12/14, 19:36:42
类
泛型

← 类 泛型→

最近更新

  01
Git修改分支名
08-11

  02
CSS给table的tbody添加滚动条
06-29

  03
我做了一个手写春联小网页，祝大家虎年暴富 原创
01-28

更多文章>

Copyright © 2016-2022 Hesb | MIT License

- 跟随系统
- 浅色模式
- 深色模式
- 阅读模式