# Функции

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

Функция в 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)


---

# 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://race-timo.gitbook.io/typescript/functions.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.
