Array and Collection Methods in TypeScript: A Detailed Guide
Что это такое и зачем? (аналогия из реальной жизни)
Представь, что ты — библиотекарь. У тебя есть огромный стеллаж с книгами (это наш массив). Каждая книга стоит на своей полке, и у каждой есть номер (индекс).
Что ты можешь делать с книгами?
- Взять книгу с полки по номеру — это
array[0] - Добавить новую книгу в конец стеллажа — это
push() - Убрать последнюю книгу — это
pop() - Найти книгу по названию — это
find() - Проверить, есть ли книга автора "Пушкин" — это
some() - Отфильтровать только книги в твёрдой обложке — это
filter() - Превратить список книг в список авторов — это
map()
Методы массивов — это готовые инструменты, которые JavaScript (и TypeScript) даёт нам, чтобы не писать каждый раз циклы вручную. Вместо того чтобы перебирать книги пальцем (цикл for), мы просто говорим: "Найди мне книгу по условию" — и метод делает всё сам.
💡 Важно: Методы делятся на две группы: мутирующие (изменяют исходный массив) и не мутирующие (возвращают новый массив, оставляя старый без изменений). В современном программировании стараются использовать не мутирующие методы — это безопаснее.
Теория: как это работает изнутри
Массив в TypeScript — это упорядоченная коллекция элементов. Каждый элемент имеет индекс (начиная с 0). Внутри компьютера массив хранится как непрерывная область памяти, где каждый элемент занимает фиксированное количество байт.
Когда мы вызываем метод, например array.push(5), происходит следующее:
- JavaScript проверяет, есть ли свободное место в текущем блоке памяти
- Если места нет — выделяется новый, больший блок памяти (обычно в 1.5-2 раза больше)
- Все старые элементы копируются в новый блок
- Новый элемент добавляется в конец
- Старый блок памяти помечается для удаления (сборщик мусора)
Методы map, filter, reduce работают по принципу функционального программирования: они принимают функцию-колбэк и применяют её к каждому элементу массива.
🔬 Сложность алгоритмов (Big O):
push/pop— O(1) (мгновенно)shift/unshift— O(n) (медленно, так как нужно сдвигать все элементы)find/indexOf— O(n) (нужно проверить каждый элемент)sort— O(n log n) (быстрая сортировка)
Синтаксис в TypeScript: все формы и варианты
TypeScript добавляет к методам JavaScript типизацию. Это значит, что мы указываем, какие типы данных хранятся в массиве, и TypeScript проверяет, что мы не положим туда что-то лишнее.
Объявление массивов
// Способ 1: квадратные скобки (рекомендуется)
let numbers: number[] = [1, 2, 3, 4, 5];
// Способ 2: generic Array
let names: Array<string> = ["Анна", "Борис", "Виктор"];
// Способ 3: readonly (неизменяемый массив)
let fixed: readonly number[] = [10, 20, 30];
// fixed.push(40); // ❌ Ошибка! Нельзя изменить readonly массив
// Способ 4: кортеж (tuple) — массив фиксированной длины с разными типами
let person: [string, number, boolean] = ["Иван", 25, true];
Таблица основных методов массивов
| Метод | Что делает | Мутирует? | Возвращает |
|---|---|---|---|
push(element) |
Добавляет элемент в конец | ✅ Да | Новую длину массива |
pop() |
Удаляет последний элемент | ✅ Да | Удалённый элемент |
shift() |
Удаляет первый элемент | ✅ Да | Удалённый элемент |
unshift(element) |
Добавляет элемент в начало | ✅ Да | Новую длину массива |
map(callback) |
Преобразует каждый элемент | ❌ Нет | Новый массив |
filter(callback) |
Оставляет элементы по условию | ❌ Нет | Новый массив |
reduce(callback, init) |
Сворачивает массив в одно значение | ❌ Нет | Одно значение |
find(callback) |
Находит первый подходящий элемент | ❌ Нет | Элемент или undefined |
some(callback) |
Проверяет, есть ли хотя бы один подходящий | ❌ Нет | boolean |
every(callback) |
Проверяет, все ли подходят | ❌ Нет | boolean |
includes(element) |
Проверяет наличие элемента | ❌ Нет | boolean |
slice(start, end) |
Вырезает часть массива | ❌ Нет | Новый массив |
splice(start, count, ...items) |
Удаляет/добавляет элементы | ✅ Да | Массив удалённых элементов |
sort(compareFn) |
Сортирует массив | ✅ Да | Тот же массив (ссылка) |
reverse() |
Переворачивает массив | ✅ Да | Тот же массив (ссылка) |
concat(...arrays) |
Объединяет массивы | ❌ Нет | Новый массив |
flat(depth) |
Разворачивает вложенные массивы | ❌ Нет | Новый массив |
flatMap(callback) |
map + flat глубины 1 | ❌ Нет | Новый массив |
Типизация колбэков
// TypeScript автоматически выводит типы параметров колбэка
const numbers: number[] = [1, 2, 3, 4, 5];
// Параметры: (элемент, индекс, весьМассив)
const doubled = numbers.map((num: number, index: number, arr: number[]) => {
console.log(`Элемент ${num} на позиции ${index} в массиве [${arr}]`);
return num * 2;
});
// Можно не указывать типы — TypeScript выведет их сам
const filtered = numbers.filter(item => item > 2);
Примеры кода — от простого к сложному
Пример 1: Базовые операции (push, pop, shift, unshift)
// Создаём массив чисел
let queue: number[] = [1, 2, 3];
// Добавляем в конец (как встать в очередь)
queue.push(4); // queue = [1, 2, 3, 4]
console.log("После push:", queue);
// Убираем с конца (как уйти из очереди)
let last = queue.pop(); // last = 4, queue = [1, 2, 3]
console.log("Удалили:", last, "Осталось:", queue);
// Добавляем в начало (как пролезть без очереди)
queue.unshift(0); // queue = [0, 1, 2, 3]
console.log("После unshift:", queue);
// Убираем из начала (как уйти первым)
let first = queue.shift(); // first = 0, queue = [1, 2, 3]
console.log("Удалили:", first, "Осталось:", queue);
Пример 2: map — преобразование данных
// У нас есть массив температур в Цельсиях
const celsius: number[] = [0, 10, 20, 30, 40];
// Преобразуем в Фаренгейты: F = C * 9/5 + 32
const fahrenheit: number[] = celsius.map(temp => {
return temp * 9/5 + 32;
});
console.log("Цельсии:", celsius); // [0, 10, 20, 30, 40]
console.log("Фаренгейты:", fahrenheit); // [32, 50, 68, 86, 104]
// Важно: исходный массив не изменился!
Пример 3: filter — отбор по условию
// Массив студентов с баллами
interface Student {
name: string;
score: number;
}
const students: Student[] = [
{ name: "Анна", score: 85 },
{ name: "Борис", score: 42 },
{ name: "Виктор", score: 73 },
{ name: "Галина", score: 91 },
{ name: "Дмитрий", score: 38 }
];
// Отбираем тех, кто сдал (балл >= 60)
const passed: Student[] = students.filter(student => student.score >= 60);
console.log("Сдали экзамен:");
passed.forEach(s => console.log(`${s.name}: ${s.score} баллов`));
// Вывод: Анна: 85, Виктор: 73, Галина: 91
// Можно комбинировать: отбираем и преобразуем
const topStudents: string[] = students
.filter(s => s.score >= 80) // сначала фильтруем
.map(s => s.name); // потом берём только имена
console.log("Лучшие студенты:", topStudents); // ["Анна", "Галина"]
Пример 4: reduce — вычисление итога
// Суммируем числа
const numbers: number[] = [10, 20, 30, 40, 50];
// reduce принимает два параметра:
// 1. Колбэк (аккумулятор, текущий элемент) => новое значение
// 2. Начальное значение аккумулятора
const sum: number = numbers.reduce((acc, current) => {
return acc + current;
}, 0); // начинаем с 0
console.log("Сумма:", sum); // 150
// Более сложный пример: группировка по свойству
const fruits: string[] = ["яблоко", "банан", "яблоко", "апельсин", "банан", "яблоко"];
const fruitCount: Record<string, number> = fruits.reduce((acc, fruit) => {
// Если фрукт уже есть — увеличиваем счётчик, иначе ставим 1
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {} as Record<string, number>);
console.log("Подсчёт фруктов:", fruitCount);
// { "яблоко": 3, "банан": 2, "апельсин": 1 }
Пример 5: find, some, every — поиск и проверка
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
const products: Product[] = [
{ id: 1, name: "Ноутбук", price: 75000, inStock: true },
{ id: 2, name: "Мышь", price: 1500, inStock: false },
{ id: 3, name: "Клавиатура", price: 3000, inStock: true },
{ id: 4, name: "Монитор", price: 25000, inStock: true }
];
// find — находит ПЕРВЫЙ подходящий элемент
const firstAvailable: Product | undefined = products.find(p => p.inStock);
console.log("Первый в наличии:", firstAvailable?.name); // "Ноутбук"
// findIndex — находит индекс первого подходящего
const mouseIndex: number = products.findIndex(p => p.name === "Мышь");
console.log("Индекс мыши:", mouseIndex); // 1
// some — есть ли хоть один товар дороже 50000?
const hasExpensive: boolean = products.some(p => p.price > 50000);
console.log("Есть дорогие товары?", hasExpensive); // true
// every — все ли товары в наличии?
const allInStock: boolean = products.every(p => p.inStock);
console.log("Все в наличии?", allInStock); // false (мышь не в наличии)
// includes — проверка наличия примитива
const ids: number[] = [1, 2, 3, 4, 5];
console.log("Есть id 3?", ids.includes(3)); // true
console.log("Есть id 10?", ids.includes(10)); // false
Пример 6: slice, splice — копирование и удаление
const letters: string[] = ["а", "б", "в", "г", "д", "е"];
// slice — НЕ мутирует, возвращает новый массив
// slice(start, end) — end не включается
const firstThree: string[] = letters.slice(0, 3);
console.log("Первые три:", firstThree); // ["а", "б", "в"]
console.log("Оригинал не изменился:", letters); // ["а", "б", "в", "г", "д", "е"]
// slice без аргументов — полная копия
const copy: string[] = letters.slice();
// splice — мутирует, удаляет/добавляет элементы
// splice(старт, сколько_удалить, ...что_добавить)
const removed: string[] = letters.splice(2, 2, "х", "у");
console.log("Удалили:", removed); // ["в", "г"]
console.log("Массив после splice:", letters); // ["а", "б", "х", "у", "д", "е"]
// toSpliced — НЕ мутирует (ES2023+)
const safeRemoved: string[] = letters.toSpliced(1, 1);
console.log("Безопасная версия:", safeRemoved);
console.log("Оригинал цел:", letters);
Пример 7: sort — сортировка с компаратором
// Внимание: sort() по умолчанию сортирует как строки!
const numbers: number[] = [10, 5, 20, 1, 30];
numbers.sort();
console.log("Сортировка по умолчанию:", numbers); // [1, 10, 20, 30, 5] — неправильно!
// Правильная сортировка чисел: передаём функцию сравнения
numbers.sort((a, b) => a - b); // по возрастанию
console.log("По возрастанию:", numbers); // [1, 5, 10, 20, 30]
numbers.sort((a, b) => b - a); // по убыванию
console.log("По убыванию:", numbers); // [30, 20, 10, 5, 1]
// Сортировка объектов по свойству
interface Person {
name: string;
age: number;
}
const people: Person[] = [
{ name: "Иван", age: 30 },
{ name: "Анна", age: 25 },
{ name: "Борис", age: 35 }
];
// Сортируем по возрасту
people.sort((a, b) => a.age - b.age);
console.log("По возрасту:", people);
// [{ name: "Анна", age: 25 }, { name: "Иван", age: 30 }, { name: "Борис", age: 35 }]
// Сортируем по имени (строки)
people.sort((a, b) => a.name.localeCompare(b.name));
console.log("По имени:", people);
// [{ name: "Анна", age: 25 }, { name: "Борис", age: 35 }, { name: "Иван", age: 30 }]
// toSorted — НЕ мутирует (ES2023+)
const sortedPeople = people.toSorted((a, b) => a.age - b.age);
Пример 8: flat и flatMap — работа с вложенными массивами
// flat — разворачивает вложенные массивы
const nested: number[][] = [[1, 2], [3, 4], [5, 6]];
const flat: number[] = nested.flat();
console.log("Развёрнутый:", flat); // [1, 2, 3, 4, 5, 6]
// flat с глубиной
const deepNested: any[] = [1, [2, [3, [4]]]];
const depth1 = deepNested.flat(1); // [1, 2, [3, [4]]]
const depth2 = deepNested.flat(2); // [1, 2, 3, [4]]
const depth3 = deepNested.flat(Infinity); // [1, 2, 3, 4]
// flatMap — map + flat глубины 1
const sentences: string[] = ["Привет мир", "Как дела", "Всё хорошо"];
const words: string[] = sentences.flatMap(sentence => sentence.split(" "));
console.log("Слова:", words);
// ["Привет", "мир", "Как", "дела", "Всё", "хорошо"]
// flatMap удобен, когда нужно вернуть несколько элементов из одного
const numbers2: number[] = [1, 2, 3];
const expanded = numbers2.flatMap(n => [n, n * 10]);
console.log("Расширенный:", expanded); // [1, 10, 2, 20, 3, 30]
Пример 9: Цепочки методов (method chaining)
interface Order {
id: number;
amount: number;
status: "pending" | "completed" | "cancelled";
}
const orders: Order[] = [
{ id: 1, amount: 5000, status: "completed" },
{ id: 2, amount: 3000, status: "pending" },
{ id: 3, amount: 8000, status: "completed" },
{ id: 4, amount: 1000, status: "cancelled" },
{ id: 5, amount: 6000, status: "completed" }
];
// Цепочка: фильтруем → преобразуем → сортируем → берём первые 2
const topCompleted: number[] = orders
.filter(order => order.status === "completed") // оставляем только выполненные
.map(order => order.amount) // берём только суммы
.sort((a, b) => b - a) // сортируем по убыванию
.slice(0, 2); // берём первые 2
console.log("Две самые большие суммы среди выполненных:", topCompleted);
// [8000, 6000]
// Можно разбить для читаемости:
const completedOrders = orders.filter(o => o.status === "completed");
const amounts = completedOrders.map(o => o.amount);
const sortedAmounts = amounts.sort((a, b) => b - a);
const topTwo = sortedAmounts.slice(0, 2);
Сравнение с Python: чем похоже и чем отличается
| Операция | TypeScript | Python |
|---|---|---|
| Добавить в конец | arr.push(x) |
arr.append(x) |
| Удалить с конца | arr.pop() |
arr.pop() |
| Добавить в начало | arr.unshift(x) |
arr.insert(0, x) |
| Преобразовать | arr.map(fn) |
map(fn, arr) или [fn(x) for x in arr] |
| Отфильтровать | arr.filter(fn) |
filter(fn, arr) или [x for x in arr if cond] |
| Свернуть | arr.reduce(fn, init) |
functools.reduce(fn, arr, init) |
| Проверить наличие | arr.includes(x) |
x in arr |
| Длина | arr.length |
len(arr) |
| Срез | arr.slice(1, 3) |
arr[1:3] |
| Сортировка | arr.sort(fn) |
arr.sort(key=fn) |
| Развернуть | arr.reverse() |
arr.reverse() |
| Объединить | arr1.concat(arr2) |
arr1 + arr2 |
| Все элементы подходят? | arr.every(fn) |
all(fn(x) for x in arr) |
| Хотя бы один подходит? | arr.some(fn) |
any(fn(x) for x in arr) |
Ключевые отличия:
- Типизация: TypeScript проверяет типы на этапе компиляции, Python — динамическая типизация
- Мутабельность: В Python почти все методы списков мутирующие. В TypeScript/JS есть и мутирующие, и нет
- Синтаксис: Python использует синтаксис с отступами и list comprehensions, TypeScript — фигурные скобки и методы
- Производительность: В JS методы массивов оптимизированы движками (V8), в Python — нативные C-функции
Типичные ошибки и подводные камни
Ошибка 1: Забыть, что sort() сортирует как строки
const nums = [10, 5, 20, 1];
nums.sort(); // ❌ [1, 10, 20, 5] — строковая сортировка!
// ✅ Правильно:
nums.sort((a, b) => a - b);
Ошибка 2: Мутация исходного массива
const original = [1, 2, 3];
const copy = original;
copy.push(4); // ❌ Мутирует и original тоже!
console.log(original); // [1, 2, 3, 4]
// ✅ Правильно: создавать копию
const safeCopy = [...original, 4];
// или
const safeCopy2 = original.concat(4);
Ошибка 3: Не обрабатывать undefined от find
const items = [{ id: 1, name: "A" }, { id: 2, name: "B" }];
const found = items.find(item => item.id === 3);
console.log(found.name); // ❌ Ошибка! found — undefined
// ✅ Правильно: проверять
if (found) {
console.log(found.name);
}
// или использовать optional chaining
console.log(found?.name); // undefined, без ошибки
Ошибка 4: Путать slice и splice
const arr = [1, 2, 3, 4, 5];
const sliced = arr.slice(1, 3); // [2, 3] — не мутирует
const spliced = arr.splice(1, 3); // [2, 3, 4] — мутирует arr!
// Запомните: slice — копирует (как ножницы), splice — вырезает (как хирург)
Ошибка 5: Забыть про shallow copy
const users = [{ name: "Иван" }, { name: "Мария" }];
const copy = [...users]; // поверхностная копия
copy[0].name = "Пётр"; // ❌ Изменяет и original[0].name!
console.log(users[0].name); // "Пётр"
// ✅ Для глубокого копирования:
const deepCopy = JSON.parse(JSON.stringify(users));
// или structuredClone (современный браузер)
const deepCopy2 = structuredClone(users);
Ошибка 6: Использовать forEach вместо map
const nums = [1, 2, 3];
const doubled = [];
nums.forEach(n => doubled.push(n * 2)); // ❌ Неправильный подход
// ✅ Правильно:
const doubledCorrect = nums.map(n => n * 2);
Лучшие практики и советы профессионалов
- Отдавайте предпочтение неизменяемости
Используйте методы, которые не мутируют исходный массив:
map,filter,slice,toSorted,toSpliced,toReversed(ES2023+). Это делает код предсказуемым и легче отлаживаемым. - Используйте readonly для защиты
function processItems(items: readonly number[]): number { // items.push(5); // ❌ Ошибка компиляции return items.reduce((sum, n) => sum + n, 0); } - Избегайте мутации в цепочках методов
// ❌ Плохо: sort мутирует const result = arr.filter(fn).sort().map(fn2); // ✅ Хорошо: используем toSorted const result = arr.filter(fn).toSorted().map(fn2); - Используйте деструктуризацию для ясности
// Вместо users.map(function(user) { return user.name; }); // Лучше users.map(({ name }) => name); - Пишите типы для сложных операций
// Явно указываем тип результата reduce const total: number = items.reduce((acc, item) => acc + item.price, 0); // Для группировки используем Record const grouped: Record<string, Item[]> = items.reduce(/* ... */, {} as Record<string, Item[]>); - Используйте flatMap вместо map + filter + flat
// ❌ Сложно const result = arr