После того как мы углубились в базовые типы данных, переменные и интерфейсы в TypeScript, настало время рассмотреть еще один важный аспект этого языка программирования — функции. Функции играют ключевую роль в организации кода, обеспечивая его модульность, повторное использование и читаемость. TypeScript предоставляет разнообразные возможности для работы с функциями, включая строгую типизацию параметров и возвращаемых значений, поддержку необязательных и параметров по умолчанию, а также возможность определения типов функций и использования функций высшего порядка.
В этой статье мы рассмотрим основные аспекты функций в TypeScript, начиная с их определения и заканчивая более продвинутыми темами, такими как контекст выполнения функций и функции высшего порядка. Мы также рассмотрим реальные примеры использования каждого концепта, чтобы лучше понять их применение на практике. Давайте начнем с обсуждения основ синтаксиса функций в TypeScript.
Объявление функции в TypeScript
В TypeScript, как и в JavaScript, функции являются важной частью. Они помогают нам организовывать код и делают его более читабельным и удобным для поддержки. Функция в TypeScript может быть объявлена несколькими способами: через ключевое слово function, как анонимная функция, или как стрелочная функция.
Самый простой способ объявить функцию — использовать ключевое слово function. Вот базовый пример функции, которая добавляет элемент в список покупок:
function addItem(item: string, list: string[]): string[] {
list.push(item);
return list;
}
let shoppingList: string[] = [];
shoppingList = addItem('Apples', shoppingList);
console.log(shoppingList); // ['Apples']
Здесь мы объявили функцию addItem, которая принимает два параметра: item (строка) и list (массив строк). Функция добавляет item в list и возвращает обновленный список.
Анонимные функции и функции-выражения
TypeScript также поддерживает анонимные функции (функции без имени) и функции-выражения. Это удобно, когда нам нужна функция для одноразового использования или когда мы хотим присвоить функцию переменной. Вот пример:
let addItem = function(item: string, list: string[]): string[] {
list.push(item);
return list;
};
let shoppingList: string[] = [];
shoppingList = addItem('Bananas', shoppingList);
console.log(shoppingList); // ['Bananas']
Стрелочные функции
Стрелочные функции — это более современный и лаконичный способ объявления функций. Они особенно полезны в качестве колбэков или методов, и часто делают код более читабельным:
let addItem = (item: string, list: string[]): string[] => {
list.push(item);
return list;
};
let shoppingList: string[] = [];
shoppingList = addItem('Oranges', shoppingList);
console.log(shoppingList); // ['Oranges']
Параметры функции в TypeScript
Теперь давайте углубимся в более сложные аспекты, такие как параметры по умолчанию, опциональные параметры и перегрузка функций. Эти возможности позволяют писать более гибкий и универсальный код.
Параметры по умолчанию
В TypeScript мы можем задавать значения по умолчанию для параметров функций. Это полезно, когда мы хотим, чтобы функция имела некоторые стандартные значения, если при вызове эти параметры не указаны. Вот пример:
function addItem(item: string, list: string[] = []): string[] {
list.push(item);
return list;
}
// Если не передать второй параметр, будет использоваться пустой массив по умолчанию.
let shoppingList = addItem('Milk');
console.log(shoppingList); // ['Milk']
В этом примере, если list не передан в функцию, используется пустой массив по умолчанию.
Опциональные параметры
Иногда не все параметры функции должны быть обязательными. В TypeScript мы можем делать параметры опциональными, добавляя знак вопроса ? после имени параметра. Опциональные параметры должны идти после обязательных:
function addItem(item: string, list?: string[]): string[] {
if (!list) {
list = [];
}
list.push(item);
return list;
}
let shoppingList = addItem('Bread');
console.log(shoppingList); // ['Bread']
В этом примере list является опциональным параметром. Если он не передан, функция создает новый пустой массив.
Перегрузка функций
Перегрузка функций позволяет объявлять несколько сигнатур для одной и той же функции, что делает её более универсальной. Давайте рассмотрим пример, где функция может принимать разные наборы параметров:
function addItem(item: string, list: string[]): string[];
function addItem(item: string, count: number, list: string[]): string[];
function addItem(item: string, param2: any, list?: string[]): string[] {
if (typeof param2 === 'number') {
for (let i = 0; i < param2; i++) {
list!.push(item);
}
return list!;
} else {
param2.push(item);
return param2;
}
}
let shoppingList: string[] = [];
shoppingList = addItem('Eggs', shoppingList);
console.log(shoppingList); // ['Eggs']
shoppingList = addItem('Banana', 3, shoppingList);
console.log(shoppingList); // ['Eggs', 'Banana', 'Banana', 'Banana']
В этом примере функция addItem перегружена так, что она может принимать либо элемент и массив, либо элемент, количество и массив. В зависимости от типа второго параметра, функция ведет себя по-разному.
Замыкания в TypeScript
Замыкание – это функция, которая «замыкает» (запоминает) контекст, в котором была создана, даже после того, как этот контекст исчез. Это позволяет функции иметь доступ к переменным из внешнего контекста даже после завершения выполнения внешней функции.
Рассмотрим пример функции для создания списка покупок, где мы используем замыкание:
function createShoppingList() {
let list: string[] = [];
return {
addItem(item: string) {
list.push(item);
console.log(`${item} added to the shopping list.`);
},
getList() {
return list;
}
};
}
let myList = createShoppingList();
myList.addItem('Cheese');
myList.addItem('Tomatoes');
console.log(myList.getList()); // ['Cheese', 'Tomatoes']
В этом примере функция createShoppingList возвращает объект с двумя методами: addItem и getList. Эти методы имеют доступ к переменной list благодаря замыканию, даже после завершения выполнения createShoppingList.
Функции высшего порядка
Функции высшего порядка – это функции, которые принимают другие функции в качестве аргументов или возвращают функции в качестве результатов. Они являются мощным инструментом для создания абстракций и повторно используемого кода.
Рассмотрим пример, где мы создаем функцию высшего порядка для фильтрации списка покупок:
type FilterFunction = (item: string) => boolean;
function filterList(list: string[], filter: FilterFunction): string[] {
return list.filter(filter);
}
let shoppingList: string[] = ['Milk', 'Bread', 'Butter', 'Cheese', 'Tomatoes'];
// Фильтр для поиска продуктов, содержащих букву 'e'
let filteredList = filterList(shoppingList, (item) => item.includes('e'));
console.log(filteredList); // ['Cheese', 'Tomatoes']
Здесь функция filterList принимает массив строк и функцию-фильтр в качестве аргументов. Она возвращает новый массив, содержащий только те элементы, которые соответствуют условию фильтра.
Контексты вызова функций
В JavaScript и TypeScript контекст вызова функции определяется значением this. Важно понимать, как this работает, особенно при работе с методами объектов и в сложных структурах.
Рассмотрим пример:
let shoppingList = {
items: ['Apples', 'Bananas', 'Oranges'],
printItems() {
this.items.forEach(function (item) {
console.log(item);
});
}
};
shoppingList.printItems(); // 'Apples', 'Bananas', 'Oranges'
В этом примере метод printItems использует this для доступа к массиву items. Внутри функции обратного вызова forEach, this по умолчанию не будет ссылаться на объект shoppingList. Мы можем исправить это, используя стрелочную функцию, которая не имеет собственного значения this, а использует значение this из внешней функции:
let shoppingList = {
items: ['Apples', 'Bananas', 'Oranges'],
printItems() {
this.items.forEach((item) => {
console.log(item);
});
}
};
shoppingList.printItems(); // 'Apples', 'Bananas', 'Oranges'
Использование стрелочной функции гарантирует, что this внутри функции обратного вызова forEach будет ссылаться на объект shoppingList.