# Функции

## Тип функции

Функция в TypeScript объявляется почти также, как и в JavaScript. Главное отличие - нужно обязательно типизировать параметры. Они типизируются при помощи `:`, по аналогии с предыдущими конструкциями.

```typescript
function foo(a: string, b: string) {
}
```

Параметры функции могут быть любыми типами. При вызове функции TypeScript будет проверять совместимость типа значения, переданного в качестве параметра, с типом этого параметра. Если типы будут не совместимы - будет ошибка компиляции

```typescript
foo("a", "b");

foo(10, "b"); //Error: Argument of type '10' is not assignable to parameter of type 'string'.
```

## Необязательные параметры и параметры со значением

Если при вызове функции будет передано меньшее или большее количество параметров, чем было указано при объявлении - TypeScript выдаст ошибку.

```typescript
function foo(a: number) {
}

foo(); //Error: Expected 1 arguments, but got 0.
foo(10, 15); //Error: Expected 1 arguments, but got 2.
```

Иногда возникает необходимость объявить функцию, у которой есть необязательные параметры. Для этого (как и для необязательных полей объекта) необходимо использовать символ `?`.

```typescript
function foo(a: number, b?: number) {
}

foo(10);
foo(10, 15);
```

В случае, когда параметр отмечен, как не обязательный, TypeScript объединяет тип параметра с типом `undefined`. Это объясняется тем, что в runtime, если необязательный параметр не был передан, то в функции его значение будет `undefined`. Однако иногда требуется задать какому-то параметру значение по умолчанию.

```typescript
function foo(a: number = 10) {
}

foo(); //a = 10
foo(15); //a = 15
```

Если параметру задается значение по умолчанию, то TypeScript может применить автовывод типа параметра по значению (по обычным правилам автовывода типа).

```typescript
function foo(a = 10){
}

foo() //a = 10
foo(15) //a = 15
foo("lalaka") //Error: Argument of type '"lalaka"' is not assignable to parameter of type 'number'
```

Все необязательные параметры должны объявляться в конце списка параметров. Пред заполненные параметры могут находится в любом месте списка параметров.

```typescript
function foo(a?: number, b: string) { // Error: A required parameter cannot follow an optional parameter.
}

function bar(a = 10, b: string) {
}

function baz(a?: number, b = true) {
}
```

## `rest` параметр

Чтобы передать в функцию неограниченное количество параметров, в JavaScript используется `rest` параметр. Для того, чтобы объявить `rest` параметр в TypeScript нужно указать его тип в виде массива. Если нужно передать в функцию неограниченное количество массивов, то нужно указать тип массив массивов.

```typescript
function foo(...rest: number[]) {
}
```

`rest` параметр должен быть самым последним параметром (даже после опциональных). Соответственно, у функции не может быть два и более `rest` параметров, так как хотя бы один из них будет не последним.

## Типизация `this`

В JavaScript реализована особая работа с контекстом выполнения функции. Поэтому при работе с TypeScript может потребоваться определить тип для `this`. Для этого необходимо в списке параметров самым первым определить параметр с названием `this` и указать тип для него. Этот параметр нужен только для того, чтобы TypeScript понял тип контекста выполнения функции. При вызове функции его передавать не нужно.

```typescript
function foo(this: number, b: number) {
    const c: number = this + b;
}

foo(10);
```

Если при определении функции был указан тип для `this`, то вызов методов `apply` и `call` могут принимать значение контекста, которое совместимо с типом, указанном при объявлении.

```typescript
function foo(this: number) {
}

foo.call(10);
foo.call("lalaka"); //Error: Argument of type '"lalaka"' is not assignable to parameter of type 'number'.

foo.apply(15);
foo.apply(true); //Error: Argument of type 'true' is not assignable to parameter of type 'number'.
```

## Типизация возвращаемого значения

Тип возвращаемого значения функции указывается сразу после списка параметров при помощи `:`

```typescript
function foo(): number {
}
```

Если тип значения, которое возвращает функция, не совместим с типом возвращаемого значения - TypeScript возвращает ошибку.

```typescript
function foo(): number {
    return "lalaka"; //Error: Type '"lalaka"' is not assignable to type 'number'
}
```

Для того, чтобы указать, что функция ничего не возвращает, используется тип `void` (не `never`).

```typescript
function foo(): void {
}

function bar(): void {
    return 10; //Error: Type '10' is not assignable to type 'void'
}
```

Если явно не указать тип возвращаемого значения функции, то TypeScript выведет его автоматически в зависимости от тела функции.

```typescript
function foo() {
} //: void

function bar() {
    return 10;
} //: number
```

## Вынесение типа функции

Как и любой другой тип в TypeScript, тип функции можно вынести в `type`. Также можно явно типизировать типом функции переменную, поле объекта, даже параметр или возвращаемое значение другой функции. Синтаксис типа функции выглядит так:

```typescript
type F = (a: string, b?: boolean) => void;
```

Для описания типа важно знать только список параметров функции, их типы и тип возвращаемого значения. Тип возвращаемого значения указывается при помощи `=>` (**важно** - `:` не используется). Проверки соответствия функции с требуемым типом осуществляется по типам параметров (включая опциональные параметры, `rest` параметры и типизацию `this`) и по типу возвращаемого значения.

```typescript
function foo (a: string) {
    return 10;
}

function bar(a: number) {
    return 10;
}

function baz(a: string, b: boolean) {
    return 10;
}

function meow(...rest: string[]) {
    return 10;
}

function woof(a: string) {
}

const f1: (a: string) => number = foo;
const f2: (a: string) => number = bar; /* Error:
Type '(a: number) => void' is not assignable to type '(a: string) => void'.
  Types of parameters 'a' and 'a' are incompatible.
    Type 'string' is not assignable to type 'number'.
*/
const f3: (a: string) => number = baz; /* Error:
Type '(a: string, b: boolean) => void' is not assignable to type '(a: string) => void'.
*/
const f4: (a: string) => number = meow;
const f5: (a: string) => number = woof; /* Error:
Type '(a: string) => void' is not assignable to type '(a: string) => number'.
  Type 'void' is not assignable to type 'number'.
*/
```

Тип функции часто используется для определения callback-ов. Например, для типизации поля `click` у DOM-элемента или типизации параметра функции `addEventListener`. Тип функции также часто используется для определения [Higher Order Function](https://en.wikipedia.org/wiki/Higher-order_function) (функции, которая возвращает функцию).

```typescript
interface Clickable {
    click?: (e: Event) => void;
    addEventListener: (event: "click", action: (e: Event) => void) => void;
}

const div: Clickable = {
    addEventListener: (event, action) => { /*...*/ }
}

div.click = e => console.log(e)
div.addEventListener("click", e => console.log(e));


function hof(...params: any[]): (...params: any) => boolean {
    return params.length > 0 ? (params => true) : (() => false);
}
```

Если у переменной, параметра, поля или возвращаемого значения явно задан тип функции, то при написании значения указывать типы параметров необязательно.

```typescript
type F = (a: {b: string}) => boolean;

const f1: F = (a: { b: string }) => a.b === "lalaka";
const f2: F = a => a.b === "lalaka";
```

Функция может содержать не все параметры, которые указаны в типе функции:

```typescript
type F = (a: number) => void
const f: F = () => { };
```

Это возможно потому, что несмотря на то, что реализация функции использует не все (а может и никакие) параметры, указанные в типе функции, вызвать функцию все равно можно только со всеми параметрами.

```typescript
f(); //Error: Expected 1 arguments, but got 0.
f(10);
```

Такая особенность позволяет не использовать все требуемые параметры, когда они не нужны. Это часто используется при описании callback-ов.

```typescript
interface MyArray {
    [x: number]: number;
    map: (item: number, index: number, array: MyArray) => number;
}

const myArray: MyArray = {
    [0]: 0,
    [1]: 1,
    map: x => x + 1,
}

interface MyDiv {
    click: (event: Event) => void;
}

const div: MyDiv = {
    click: () => console.log("click")
}
```

## Перегрузка

В JavaScript можно объявить функцию, которая может возвращать значения разных типов. Более того, один и тот же параметр может принимать значения разных типов. Например, опишем функцию `lalaka`, которая принимает 2 параметра: параметр `a: "string" | "number"`, который указывает тип для значения второго параметра, и второй параметр `b: string | number` в который передается значение того типа, который указан в первом параметра. Если первый параметр `"string"`, то второй параметр должен быть строкой, а возвращаемое значение - объект вида `{a: b}`. А если первый параметр `"number"`, то второй параметр - число и в результате функция должна вернуть результат операции `b + 10`. Для описания такой функции в TypeScript можно попробовать воспользоваться объединением типов или типом `any`.

```typescript
function lalaka(a: "string" | "number", b: string | number): any {
    if (a === "string") {
        return {
            a: b
        }
    } else {
        return b + 15; //Error: Operator '+' cannot be applied to types 'string | number' and 'number'.
    }
}

lalaka("number", "lalaka")
lalaka("string", 150).c === "lalaka"
```

В данном случаи TypeScript не может гарантировать, что если в пером параметра передано значение `"number"`, то второй параметр будет `number`, то есть оба параметра считаются независимыми. Поэтому в объявлении функции ошибка. Более того, возвращаемое значение функции `any`, из-за чего с результатом функции можно делать что угодно (например, сравнивать значение несуществующего поля).

Для того, чтобы улучшить типизацию этой функции можно воспользоваться перегрузкой. Перегрузка - это возможность указать несколько типов (несколько сигнатур) для одной и той же функции. Компилятор TypeScript будет сопоставлять типы переданных параметров с перегрузками функции и, если у функции не будет подходящего типа, выдаст ошибку.

Для того, чтобы объявить перегрузки для функции, нужно сначала указать сигнатуры перегрузок функции, а затем написать функцию реализацию.

```typescript
function foo(a: number): number;
function foo(a: string): string;
function foo(a: number | string) {
    return a;
}
```

В данном случаи первая и вторая сигнатура функции - это доступные для использования перегрузки (объявляются без тела функции), а последняя - это функция реализация. В функции-реализации описывается то, что на самом деле будет выполняться в runtime. Как было сказано выше, компилятор будет выбирать перегрузку подходящую под переданные параметры. Соответственно, если в функцию `foo` будет передано значение типа `number`, то компилятор выберет первую перегрузку с возвращаемым значением типа `number`

```typescript
const a: number = foo(10) + 15;
const b: string = foo(10); // Error: Type 'number' is not assignable to type 'string'.
const c: string = foo("lalaka");
```

Сигнатура функции-реализации должна учитывать все сигнатуры перегрузок. То есть типы всех параметров и тип возвращаемого значения функции-реализации должны быть совместимы с соответствующими типами всех перегрузок. Если функция-реализация не будет соответствовать хотя бы одной перегрузке - будет ошибка.

```typescript
function foo(a: number): boolean; //Error: This overload signature is not compatible with its implementation signature.
function foo(a: string): boolean {
    return true;
}

function bar(a: number): boolean; //Error: This overload signature is not compatible with its implementation signature.
function bar(a: number): string {
    return "lalaka";
}
```

С использованием перегрузок функции пример функции `lalaka` будет выглядеть так:

```typescript
function lalaka(a: "number", b: number): number
function lalaka(a: "string", b: string): {b: string};
function lalaka(a: "string" | "number", b: any) {
    if (a === "string") {
        return {
            a: b
        }
    } else {
        return b + 15;
    }
}

lalaka("number", "lalaka") //Error: No overload matches this call.
lalaka("string", 150).c === "lalaka" //Error: No overload matches this call.

const a: number = lalaka("number", 10);
const b: string = lalaka("string", "lalaka").b
```

## Функция как объект

Ранее было сказано, что при помощи `type` и `interface` можно описать тип объекта. Но функция тоже является объектом. Можно ли ее описать при помощи этих конструкций? Да, можно.

```typescript
interface B {
    (a: number, b: string): boolean
}

const b: B = (a: number, b: string) => a.toString() === b;
```

Интерфейс объекта, который может быть вызван, называется `callable`. Для того, чтобы указать сигнатуру вызова объекта, нужно в `()` указать параметры и их типы, а через `:` (**не** через `=>`) указать возвращаемое значение. В `callable` можно указать больше чем одну сигнатуру вызова, таким образом получить описание перегрузок.

```typescript
interface A {
    (a: number): number;
    (b: string): string;
}

const foo: A = (a: any) => a;

const a: number = foo(10);
const b: string = foo("lalaka");
```

При помощи `callable` можно описать тип функции, которая не просто может быть вызвана, но и иметь собственные поля (отличающиеся от стандартных полей функции, например, `length`). Однако, значение, которое будет подходить для такого типа, достаточно сложно (с точки зрения типизации) собирать. Один из способов получения значения для такого типа - использовать [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE).

```typescript
interface A {
    (a: number): number;
    readonly lalaka: string;
}

const foo: A = (() => {
    const f = (a: number) => a;
    f.lalaka = "lalaka";
    return f;
})();

const a: number = foo(10);
const b: string = foo.lalaka;
```

## Задания

* [Задание #5](https://www.typescriptlang.org/play?strictNullChecks=false#code/PQKgsAUABDWMQgguEEAwgg2EEBwgghEEKwgVCCIGwZhBBeECQ0GEQQARBBpECkBEQMohQMRA1BOECgEMBGAWg4FYANFEDiIBkB8IIEYQGkRzTA3CDio4wBIgaSeIRQE4wDwggfhA08wPIgLNJBDBIAMwCuAOwDGAFwCWAe3ucuACgCUUADeUAC+Ng4uHl4cAEz+QaHhTm6enADM8cFhEHbJUZwALJmJOREp0fzF2ZDcPlwADH4A3DW+-FAA1FAxzTVxDcLOAE62AKa9ELE+AEQANhzzANYc08LWCwDO4y2TGRMcGYEh+4dQ1lwAXFDTALYLHMvToROOnhvOUAD6HAVX9rY3ABGoyGUAAvIV-DtXvZ3l8BFd3kNXPYAObgziVAB0OIA2nN7o9hLdCSsALoTSCgSCwKCAPBBDGpsFAMGh9DQAEIAQWEHI5UDQUA5AGFhGIpDI5FRFMo1BotDoDEZTGhzBBLJBnABPAAOoyFXIx-1msx22r1Qv5EONps1uv1IqNthNO0gMLhgMuBox8TBAD5rvMlitoW8PoCYlduRiOOCAwTg08uhwdp66o1U-1+JSIO7w2ko1aoL6A7iuPwyaHYeHfpaY3HOKmMmX6vUKamijm81BAfwo8KfQF-VAGlWPQA2fv14cpt1hnsAdinEJ8HGEgKHAdjXUBY-DAA5l8W1z3hI5N5xOj2r45U5Vx-VhAvHyPW8Jy8IYl-hGk0gUc9S0CwIgqCYDguAYBQ4gEGgOD6FgFBYAQFBkIA7CAIIK9CMGYUAAAaOLhYoSNIUCyFAChKKo6iaNoeiGCYOE0rAmEMMwaCAHIgeEEaRWDyIAMiCocY5HSkogBMIFQqGiaQfF4NoSAYbogpkAQ4iACwgRBKPoSAUEguhYJBulqhqpR5Kkjj+H8ALAkMOy5JEZk+DiWJDKM7xXBw9hariFLuZ5QSQJA1S5vOjhev8QIghi5kvCFkZQLi4XWWSUXpjF1ZQI4BZQEiKLohC5nTDlaKrIGpLTGlcKOLWiWRflMw1UMJUNBVHyOH2PbuO4syjB5KXTICnXdR5JXrLMWwtRlk4dV1PVeHV0weVqSbXEVqIlcMYwAeAQEwAyaBMjgjgLPMgLdTwaBENgFC0FgrC4DQAAiXI8A9D1ERKUD6BQgpoLg+jiPxqHiFJRCqp9CFYHpRBEFQgCiIFAAAmvAIwUTEwGRFGytRCp0cqZgWFYEAos4ILrI4+pPf5EBBcTpMcOTUAPfygSBQFRP2CTQxkxTA4s9TbO01z9MUw9VNBd2SNXJT1rOrawXpQjcVM06LpzgrWUPQOMuq-LcIo1Lova3LkBIz47RdNmOyKz445XuOE2fIrlkRaCELW9MC7LR75V7l8CNZQNM29W7GQbaMaybOHUBhxHY1RzH0cjPHSeDCnZyRxNszO9ZGIo1i3Vos4AAWvu4gAHsIWrJW7BRYjq7gbK45S+yjnxeqtudFBmat6wUnxxQ1nd1BNrcB4Ns1Dw09RXvwrbNEAA)

* [Тест](https://docs.google.com/forms/d/e/1FAIpQLScVFtG0snYPZ60B9nP5AEKOjSo4n_dKAOsmVCeU1q0k4WmTZA/viewform?usp=sf_link)

* [Пример решения задания #5](https://www.typescriptlang.org/play?strictNullChecks=false#code/PQKgsAUABDWMQgguEEAwgg2EEBwgVCCIGwzCCF4QJQIRBBhEEAEQQaRApAREGLwUDEQNQThAoBDARgFpWBWAGiiBxEEKA+EECMIJTyBWECgTA3CAioIwBIgaMSIRQEIwDwggfhA0cwPIgjNJBDBIAMwCuAOwDGAFwCWAeztt2AClYAuKDsbAFsAIwBTACcASigAbygAX2t7Z3dPVgAmXwCgsKioAB8oAGcnSJc7AHMBUIDQtzcAG3DWO1iE5IhbR1cPNgBmXwB+AISrdlHS8sqqpI6klN70tgAWbwXI8KcbSM92AAYAbkXu1L6Mnm8AOlutsoCyiuqAbQBdTe3dz3unF4O3icupAON5DtEjiCfDwoABqKCZCEg7KHATlGzhJEQLLeABETVYBIA1qxcQIrISSpjIdihljWEM4ol6YyoBMArjgoTWCTcfMaQ4PGUoAB9VirXIhCKRKAAXjWGwFQqcot4jxm1TlbCut2uL3x3N5Ak5htJHxpkFAkFgUEAeCAGVRQQhoPSUABCAEEBG63VAMG6AMICYTiSQyeSKFRqDRaXQGYymcyWCBOACeAAdwlBPVqcoEpVFYrKAHzTZ5VGlpzPZ33ykYBF55aUfOWlxsFyKAyBVrOB3O636S-KRd5F0tNqIWiCCuzC0LsAI5utjqAG4mkpWzlWhTKLj1a1it1cE9d8+GsGnzsEHLE7sE8LGQGdzgaL2tQDZHl7sHhd6fKqBQglGsDyPC9IFCIZvwOAFb3WR9-y3QCeEXANcxXQ5NznAA2VDQJLNgsO3AB2PC61YWoV0PeFQiIwCAA4yI-CjAIEBwqLhQDOIcS8rmwg4BGIgSoEOYSfwETJJIEAYBlWBCrWgWBEFQDBMEIUgRBwNAZD0KRSCkHBSGIQB2EAQDAaDoUwoAAAwcazg1ECQoGkWRyAUJRVHUTRtH0QwTDQDBrVgczaAYNBADkQGy7OcqQ5EAGRBjKMVz3MAJhByGMlKiDirAtCQMydAwYgcBEQAWEDwRQ9CQUgkB0KR1JqswIAsJY0n6BwNiHaUaR6VrPHa-x82HaIGwnTtuvOFZ+o5J5ZjJQD1XLYay1mcbljavNcVGua6kG6UltG1beqgKbluqWp6kaFo2iWhpmlaOxDouY6bjucIHjYOxU1HAI2lTeJIEgYFEOFBwF12gp5XarFnxVBxdygdthzeLV2vBOiHFfU65khvEZuqOa1x5UloYAhxgNGlG8S2gQ0afUmUMAy77sp3Fbquuw5opJoqRJpCHFwxm7raFnfrPVc8aqOb0WpAGIAUm17TQR0HEJAlQhaTg0DwQg9KoKQmEwSgABEPU4I2jYc0MoD0UgMDQTA9BEeLjJETK8AC629KkWq8DwchAFEQKAABMuCD1YgpgFyIw86NvLjPzEya5NKicKIKQcLMTf+xSYDzUaloANzcFwg5pIGU7T1gM6gI3fTiCPmM6wsAiLkuaRtPMJf2jsy9livInTzO0PrnOPwHN6nAuoW7G+wX2d7iBIH7wea6N7ObRaaonAACybyJ29gdM3BKFwLhGjsBAlv8O4Gm8FpWhu83YbvhwP3OBvzqf58B2WYeDsGs7yiCE0JodEg7w1rlqYBoC6ZISDpjI2aEgE2BAWA4C5soEoJgYvCAIdvAwnhA+Gk4DvDYU4thXmwoRTgL3lqEhuJiJiwYbiShKpqGYzZszeU8DvDS3JJScIaJIgYn4dzQRUA+ESOEeIyRkiuY8zok0Wh3DVjXE3lUHedEXgAA8BCpmRio64R8T4XDQSKMGEs6HrBvGY+GFMVFglYcHVYIoOFM2Fg40SnEeAwQhEAA)
