Как получить последнее совпадающее значение в массиве JavaScript
Вам нужно найти последний элемент в массиве, который соответствует условию. Возможно, это самая последняя запись об ошибке, последний валидный ввод формы или последний активный пользователь. До ES2023 это требовало неудобных обходных решений — разворота массивов, фильтрации всего содержимого или написания ручных циклов. Теперь есть более чистый способ.
Ключевые моменты
- Используйте
findLast()для получения последнего элемента массива, соответствующего условию, без изменения исходного массива. - Используйте
findLastIndex(), когда вам нужна позиция, а не само значение. - Избегайте использования
reverse().find(), так как это изменяет исходный массив и приводит к трудноуловимым ошибкам. - Помните о различных возвращаемых значениях:
findLast()возвращаетundefined, когда совпадение не найдено, тогда какfindLastIndex()возвращает-1.
Современное решение: Array.prototype.findLast()
Array.prototype.findLast() перебирает массив в обратном порядке и возвращает первый элемент, который удовлетворяет вашему предикату. Это зеркальное отражение find(), который ищет с начала.
const logs = [
{ type: 'info', message: 'Started' },
{ type: 'error', message: 'Connection failed' },
{ type: 'info', message: 'Retrying' },
{ type: 'error', message: 'Timeout' }
]
const lastError = logs.findLast(log => log.type === 'error')
// { type: 'error', message: 'Timeout' }
Если ни один элемент не совпадает, findLast() возвращает undefined. Это делает безопасным использование с optional chaining:
const lastError = logs.findLast(log => log.type === 'critical')?.message ?? 'No critical errors'
findLast против findLastIndex: когда нужна позиция
Иногда вам нужен индекс, а не значение. Для этого предназначен findLastIndex(). Он возвращает индекс последнего совпадающего элемента или -1, если ничего не найдено.
const numbers = [1, 5, 3, 8, 2, 9, 4]
const lastLargeIndex = numbers.findLastIndex(n => n > 7)
// 5 (индекс элемента 9)
Используйте findLastIndex(), когда вам нужно выполнить splice, slice с этой позиции или обратиться к соседним элементам.
Совместимость с браузерами и fallback-решения
Оба метода findLast() и findLastIndex() являются частью ES2023 и имеют широкую поддержку в современных браузерах и последних версиях Node.js. Однако старые окружения могут их не поддерживать.
Если TypeScript выдает ошибки при использовании этих методов, вероятно, в вашем tsconfig.json нужно добавить "lib": ["ES2023"] или новее в опции компилятора.
Для окружений без нативной поддержки вот чистое fallback-решение, которое не изменяет исходный массив:
function findLast(arr, predicate) {
for (let i = arr.length - 1; i >= 0; i--) {
if (predicate(arr[i], i, arr)) {
return arr[i]
}
}
return undefined
}
Этот обратный цикл эффективен — он останавливается при первом совпадении и не создает промежуточные массивы.
Discover how at OpenReplay.com.
Распространенные ошибки, которых следует избегать
Не разворачивайте массив для использования find():
// ❌ Изменяет исходный массив!
const last = arr.reverse().find(x => x > 5)
// ✅ Используйте findLast вместо этого
const last = arr.findLast(x => x > 5)
Вызов reverse() изменяет ваш массив на месте. Это создает трудноуловимые ошибки, которые сложно отлаживать.
Не путайте возвращаемые значения:
findLast()возвращает элемент илиundefinedfindLastIndex()возвращает индекс или-1
Проверка на истинность работает для findLast(), но с findLastIndex() необходимо явно проверять на -1:
const index = arr.findLastIndex(x => x > 100)
if (index !== -1) {
// Найдено
}
Остерегайтесь ложных совпадений:
Если ваш массив содержит 0, false или пустые строки, убедитесь, что ваш предикат корректно их обрабатывает:
const values = [1, 2, 0, false, 'last']
values.findLast(x => x) // 'last' (пропускает ложные значения)
values.findLast(() => true) // 'last' (получает фактически последний элемент)
Когда выбирать каждый подход
| Требуется | Метод |
|---|---|
| Последний элемент, соответствующий условию | findLast() |
| Индекс последнего совпадающего элемента | findLastIndex() |
| Просто последний элемент (без условия) | arr.at(-1) или arr[arr.length - 1] |
| Поиск точного значения | lastIndexOf() |
Заключение
Чтобы получить последнее совпадающее значение в массиве JavaScript, в первую очередь используйте findLast(). Он читаем, не изменяет ваши данные и останавливается, как только находит совпадение. Когда вам нужна позиция, а не значение, используйте findLastIndex(). Для старых окружений простой обратный цикл обеспечивает надежное fallback-решение без рисков мутации, связанных с reverse(). Делайте ваши предикаты явными и помните о различных возвращаемых значениях — undefined против -1 — чтобы избежать скрытых ошибок.
Часто задаваемые вопросы
Да, findLast() работает с разреженными массивами. Он перебирает массив в обратном порядке и пропускает пустые ячейки, вызывая предикат только для элементов, которые фактически существуют. Это поведение аналогично тому, как find() обрабатывает разреженные массивы при прямом переборе.
findLast() более эффективен, потому что он прекращает итерацию, как только находит совпадение. Использование filter().pop() сначала обрабатывает весь массив, создавая новый массив в памяти, а затем извлекает последний элемент. Для больших массивов findLast() обеспечивает значительный прирост производительности.
Да, findLast() поддерживает дженерики TypeScript и сужение типов. Когда вы используете type guard в качестве предиката, TypeScript корректно выводит суженный тип для возвращаемого элемента. Убедитесь, что ваш tsconfig.json включает ES2023 в массиве lib для правильных определений типов.
Да, вы можете добавить полифил в Array.prototype, если метод не существует. Сначала проверьте наличие метода, затем назначьте функцию, которая перебирает массив в обратном порядке. Библиотеки Core-js и другие библиотеки полифилов также предоставляют готовые реализации для более широкой совместимости.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.