# 函式的型別

> [函式是 JavaScript 中的一等公民](https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch2.html)

## 函式宣告

在 JavaScript 中，有兩種常見的定義函式的方式——函式宣告（Function Declaration）和函式表示式（Function Expression）：

```javascript
// 函式宣告（Function Declaration）
function sum(x, y) {
    return x + y;
}

// 函式表示式（Function Expression）
let mySum = function (x, y) {
    return x + y;
};
```

一個函式有輸入和輸出，要在 TypeScript 中對其進行約束，需要把輸入和輸出都考慮到，其中函式宣告的型別定義較簡單：

```typescript
function sum(x: number, y: number): number {
    return x + y;
}
```

注意，**輸入多餘的（或者少於要求的）引數，是不被允許的**：

```typescript
function sum(x: number, y: number): number {
    return x + y;
}
sum(1, 2, 3);

// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
```

```typescript
function sum(x: number, y: number): number {
    return x + y;
}
sum(1);

// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
```

## 函式表示式

如果要我們現在寫一個對函式表示式（Function Expression）的定義，可能會寫成這樣：

```typescript
let mySum = function (x: number, y: number): number {
    return x + y;
};
```

這是可以透過編譯的，不過事實上，上面的程式碼只對等號右側的匿名函式進行了型別定義，而等號左邊的 `mySum`，是透過賦值操作進行型別推論而推斷出來的。如果需要我們手動給 `mySum` 新增型別，則應該是這樣：

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

注意不要混淆了 TypeScript 中的 `=>` 和 ES6 中的 `=>`。

在 TypeScript 的型別定義中，`=>` 用來表示函式的定義，左邊是輸入型別，需要用括號括起來，右邊是輸出型別。

在 ES6 中，`=>` 叫做箭頭函式，應用十分廣泛，可以參考 [ES6 中的箭頭函式](http://es6.ruanyifeng.com/#docs/function#%E7%AE%AD%E9%A0%AD%E5%87%BD%E5%BC%8F)。

## 用介面定義函式的形狀

我們也可以使用介面的方式來定義一個函式需要符合的形狀：

```typescript
interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}
```

## 可選引數

前面提到，輸入多餘的（或者少於要求的）引數，是不允許的。那麼如何定義可選的引數呢？

與介面中的可選屬性類似，我們用 `?` 表示可選的引數：

```typescript
function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
```

需要注意的是，可選引數必須接在必需引數後面。換句話說，**可選引數後面不允許再出現必需引數了**：

```typescript
function buildName(firstName?: string, lastName: string) {
    if (firstName) {
        return firstName + ' ' + lastName;
    } else {
        return lastName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName(undefined, 'Tom');

// index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.
```

## 引數預設值

在 ES6 中，我們允許給函式的引數新增預設值，**TypeScript 會將添加了預設值的引數識別為可選引數**：

```typescript
function buildName(firstName: string, lastName: string = 'Cat') {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
```

此時就不受「可選引數必須接在必需引數後面」的限制了：

```typescript
function buildName(firstName: string = 'Tom', lastName: string) {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');
```

> 關於預設引數，可以參考 [ES6 中函式引數的預設值](http://es6.ruanyifeng.com/#docs/function#%E5%87%BD%E5%BC%8F%E5%BC%95%E6%95%B8%E7%9A%84%E9%A0%90%E8%A8%AD%E5%80%BC)。

## 剩餘引數

ES6 中，可以使用 `...rest` 的方式獲取函式中的剩餘引數（rest 引數）：

```javascript
function push(array, ...items) {
    items.forEach(function(item) {
        array.push(item);
    });
}

let a = [];
push(a, 1, 2, 3);
```

事實上，`items` 是一個數組。所以我們可以用陣列的型別來定義它：

```typescript
function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}

let a = [];
push(a, 1, 2, 3);
```

注意，rest 引數只能是最後一個引數，關於 rest 引數，可以參考 [ES6 中的 rest 引數](http://es6.ruanyifeng.com/#docs/function#rest%E5%BC%95%E6%95%B8)。

## 過載

過載允許一個函式接受不同數量或型別的引數時，作出不同的處理。

比如，我們需要實現一個函式 `reverse`，輸入數字 `123` 的時候，輸出反轉的數字 `321`，輸入字串 `'hello'` 的時候，輸出反轉的字串 `'olleh'`。

利用聯合型別，我們可以這麼實現：

```typescript
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}
```

然而這樣有一個缺點，就是不能夠精確的表達，輸入為數字的時候，輸出也應該為數字，輸入為字串的時候，輸出也應該為字串。

這時，我們可以使用過載定義多個 `reverse` 的函式型別：

```typescript
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}
```

上例中，我們重複定義了多次函式 `reverse`，前幾次都是函式定義，最後一次是函式實現。在編輯器的程式碼提示中，可以正確的看到前兩個提示。

注意，TypeScript 會優先從最前面的函式定義開始匹配，所以多個函式定義如果有包含關係，需要優先把精確的定義寫在前面。

## 參考

* [Functions](http://www.typescriptlang.org/docs/handbook/functions.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Functions.html)）
* [Functions # Function Types](http://www.typescriptlang.org/docs/handbook/interfaces.html#function-types)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html#%E5%87%BD%E5%BC%8F%E5%9E%8B%E5%88%A5)）
* [JS 函數語言程式設計指南](https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/)
* [ES6 中的箭頭函式](http://es6.ruanyifeng.com/#docs/function#%E7%AE%AD%E9%A0%AD%E5%87%BD%E5%BC%8F)
* [ES6 中函式引數的預設值](http://es6.ruanyifeng.com/#docs/function#%E5%87%BD%E5%BC%8F%E5%BC%95%E6%95%B8%E7%9A%84%E9%A0%90%E8%A8%AD%E5%80%BC)
* [ES6 中的 rest 引數](http://es6.ruanyifeng.com/#docs/function#rest%E5%BC%95%E6%95%B8)
* [上一章：陣列的型別](https://willh.gitbook.io/typescript-tutorial/basics/type-of-array)
* [下一章：型別斷言](https://willh.gitbook.io/typescript-tutorial/basics/type-assertion)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://willh.gitbook.io/typescript-tutorial/basics/type-of-function.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
