Получение одиночных элементов
Этот урок показывает, как получить один элемент из массива: по его позиции, по условию или случайным образом. Он также показывает, как проверить, существует ли элемент вообще.
В TypeScript есть два значения, означающие «нет значения»: null и undefined. Когда поиск может не удаться, ответом будет undefined. Рядом со значением нет второго флага: отсутствующий элемент и есть undefined.
Чтение позиции, которой не существует, например words[5] в массиве из 5 элементов, не останавливает программу. Вы получаете undefined. Тип string | undefined нужен именно для того, чтобы напоминать вам об этом случае.
Чтобы превратить этот undefined в собственное значение, используйте оператор ??. Он берёт правую часть, только когда левая равна null или undefined.
Массивы несут большинство этих инструментов в виде методов: at(), find(), findLast(), includes() и every(). Для остального вы один раз пишете небольшую вспомогательную функцию.
Получение по позиции
У каждого элемента массива есть позиция, которая называется индексом. Первый индекс — 0, поэтому последний равен words.length - 1.
Чтобы прочитать элемент по позиции, напишите индекс в квадратных скобках: words[3]. Для последнего элемента есть также words.at(-1), который отсчитывает от конца.
Несуществующий индекс даёт undefined, а не ошибку. Поэтому честный тип для words[i] — это string | undefined: может быть, там строка, а может быть, ничего.
Методов elementAtOrNull() и elementAtOrElse() нет. Они и не нужны: отсутствующий элемент уже равен undefined, а ?? подставляет вместо него ваше собственное значение.
Элемент по позиции
words[3] читает элемент по индексу 3. Поскольку отсчёт начинается с 0, это четвёртое слово, поэтому программа выводит four.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five"]
// Позиции начинаются с 0, поэтому индекс 3 — это четвёртый элемент.
console.log(words[3])
Первый и последний элемент
Первый элемент — это words[0]. Последний — words.at(-1): at() принимает отрицательный индекс и отсчитывает от конца, поэтому -1 — это последний элемент. Программа выводит one, а затем five.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five"]
// Первый элемент находится по индексу 0. at(-1) отсчитывает от конца.
console.log(words[0])
console.log(words.at(-1))
Элемент по позиции или undefined
elementAt() просто читает items[i] и говорит об этом в своём возвращаемом типе: T | undefined. Индекс 1 находится в пределах массива, поэтому первый вызов выводит two. Индекс 5 выходит за пределы, поэтому второй вызов выводит undefined — без падения и без второго true или false рядом со значением.
Вывод: программа на TypeScript и захваченный вывод
// Чтение по индексу даёт элемент или undefined — второго значения «ok» нет.
function elementAt<T>(items: T[], i: number): T | undefined {
return items[i]
}
const words = ["one", "two", "three", "four", "five"]
console.log(elementAt(words, 1))
// Индекса 5 не существует: программа не падает, просто undefined.
console.log(elementAt(words, 5))
Элемент по позиции или запасное значение
words[5] равно undefined, поэтому ?? берёт правую часть и вызывает fallback(5). Функция получает индекс, поэтому может построить сообщение Значение для индекса 5 равно undefined.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five"]
const fallback = (i: number): string => `Значение для индекса ${i} равно undefined`
// ?? берёт правую часть, только если левая равна null или undefined.
console.log(words[5] ?? fallback(5))
Получение по условию
Часто вы не знаете позицию. Зато вы знаете условие: первое слово длиннее 3 букв, последнее слово, начинающееся с "f". Условие — это функция, которая принимает один элемент и возвращает true или false.
Для первого совпадения у массивов есть find(). Он идёт от начала и возвращает первый элемент, прошедший проверку, или undefined, если не проходит ни один.
findLast() делает тот же обход в другую сторону, от последнего индекса к 0. Первый встреченный элемент — это последнее совпадение в массиве.
Оба метода останавливаются, как только находят совпадение, поэтому остаток массива никогда не проверяется. А поскольку «ничего не совпало» — это просто undefined, вы проверяете результат через !== undefined или задаёте значение по умолчанию через ??.
Первое совпадение
find() возвращает первое слово, для которого s.length > 3. "one" и "two" слишком короткие, поэтому программа выводит three.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five"]
// find() возвращает первый элемент, который проходит проверку.
console.log(words.find(s => s.length > 3))
Последнее совпадение
findLast() начинает с последнего индекса и движется вниз. Первое встреченное слово, начинающееся с "f", — это "five", поэтому он останавливается там и программа выводит five.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five"]
// findLast() идёт с конца, поэтому первое попадание — это последнее совпадение.
console.log(words.findLast(s => s.startsWith("f")))
Первое совпадение или undefined
Ни одно слово не длиннее 6 букв, поэтому find() возвращает undefined, а ?? "не найдено" заставляет программу вывести не найдено. В массиве чисел первое чётное число — 2; проверка !== undefined — это то, как программа узнаёт, что что-то найдено.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five"]
const numbers = [1, 2, 3, 4]
// Ничего не подходит, поэтому find() возвращает undefined, а ?? подставляет сообщение.
const long: string | undefined = words.find(s => s.length > 6)
console.log(long ?? "не найдено")
// «Найдено» означает просто «не undefined».
const firstEven: number | undefined = numbers.find(n => n % 2 === 0)
if (firstEven !== undefined) {
console.log(firstEven)
}
Последнее совпадение или undefined
findLast() проходит числа в обратном порядке, поэтому первое встреченное чётное число — это последнее в массиве: 4, по индексу 3. Оно находится сразу же.
Вывод: программа на TypeScript и захваченный вывод
const numbers = [1, 2, 3, 4]
// findLast() идёт с конца и возвращает undefined, если ничего не подходит.
const lastEven: number | undefined = numbers.findLast(n => n % 2 === 0)
if (lastEven !== undefined) {
console.log(lastEven)
}
Получение первого присутствующего значения
Иногда в массиве есть дыры: несколько элементов равны null или undefined, а вам нужно первое настоящее значение.
Обход снова выполняет find(). Условие — v !== null && v !== undefined.
Если записать его прямо в стрелочную функцию, результат всё равно имел бы тип number | string | null | undefined: проверка выполняется во время выполнения, но тип от этого не меняется. Перенесите проверку в именованную функцию, возвращаемый тип которой — v is number | string (такая функция называется стражем типа), и тип результата сам потеряет null и undefined.
Массив в примере одновременно содержит число, строку и null, поэтому его тип — объединение всех этих типов.
Первое значение, не равное null или undefined
Первый элемент равен null, поэтому он пропускается. Следующий — 0, который не равен ни null, ни undefined, поэтому find() возвращает его и останавливается — "true" так и не проверяется. Обратите внимание, что 0 сохраняется: проверка спрашивает про null и undefined, а не про истинность или ложность.
Вывод: программа на TypeScript и захваченный вывод
// null и undefined оба означают «нет значения». Любое другое значение считается настоящим.
const list: (number | string | null | undefined)[] = [null, 0, "true"]
// Без «v is number | string» результат сохранил бы в типе null и undefined.
const isPresent = (v: number | string | null | undefined): v is number | string =>
v !== null && v !== undefined
const first: number | string | undefined = list.find(isPresent)
console.log(first)
Случайный элемент
Чтобы выбрать случайный элемент, выберите случайный индекс. Допустимый индекс — это целое число от 0 до numbers.length - 1.
У массивов нет метода random(), поэтому индекс, как обычно, попадает в квадратные скобки.
Math.random() даёт другое число при каждом запуске. Именно это и нужно в настоящей программе, но тогда этот урок каждый раз выводил бы что-то другое. Поэтому пример инициализирует собственный крошечный генератор фиксированным числом 42. Одно и то же зерно всегда даёт один и тот же индекс, что делает результат воспроизводимым. Используйте Math.random(), когда вам действительно нужен разный элемент при каждом запуске.
Выбор случайного элемента
С зерном 42 генератор выбирает индекс 1, поэтому программа выводит 2. Измените зерно — и может получиться другое число.
Вывод: программа на TypeScript и захваченный вывод
const numbers = [1, 2, 3, 4]
const MOD = 4294967296
let seed = 42
// Наш собственный крошечный генератор: одно и то же зерно всегда даёт один и тот же индекс.
function nextIndex(n: number): number {
seed = (seed * 1664525 + 1013904223) % MOD
return Math.floor((seed / MOD) * n)
}
const i = nextIndex(numbers.length)
console.log(numbers[i])
Проверка наличия элемента
Чтобы проверить, есть ли значение в массиве, используйте includes(). Он возвращает true или false.
Метода containsAll() нет, но every() даёт вам его в одну строку: wanted.every(v => words.includes(v)) равно true, только когда найдено каждое искомое значение. every() останавливается на первом отсутствующем значении.
Метода isEmpty() тоже нет. Сравнивайте длину: words.length === 0 означает «пусто», а words.length > 0 — «не пусто».
Содержит одно значение
"four" есть в массиве, поэтому первая проверка выводит true. "zero" в массиве нет, поэтому вторая выводит false.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five"]
console.log(words.includes("four"))
console.log(words.includes("zero"))
Содержит все значения
И "four", и "one" есть в массиве, поэтому containsAll() возвращает true. "zero" отсутствует, поэтому второй вызов возвращает false.
Вывод: программа на TypeScript и захваченный вывод
// every() возвращает true, только если проверка проходит для каждого искомого значения.
function containsAll<T>(items: T[], wanted: T[]): boolean {
return wanted.every(v => items.includes(v))
}
const words = ["one", "two", "three", "four", "five"]
console.log(containsAll(words, ["four", "one"]))
console.log(containsAll(words, ["one", "zero"]))
Пусто или не пусто
У empty длина 0, поэтому empty.length === 0 равно true. В words 5 элементов, поэтому words.length > 0 тоже равно true.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five"]
const empty: string[] = []
// Массив пуст, когда его длина равна 0.
console.log(empty.length === 0)
console.log(words.length > 0)