12k
All articles

Как получить последнее совпадающее значение в массиве JavaScript

Методы findLast и findLastIndex позволяют получить последний подходящий элемент массива или его индекс без мутации данных и ручного написания циклов.

OpenReplay Team
OpenReplay Team
Как получить последнее совпадающее значение в массиве 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
}

Этот обратный цикл эффективен — он останавливается при первом совпадении и не создает промежуточные массивы.

Распространенные ошибки, которых следует избегать

Не разворачивайте массив для использования find():

// ❌ Изменяет исходный массив!
const last = arr.reverse().find(x => x > 5)

// ✅ Используйте findLast вместо этого
const last = arr.findLast(x => x > 5)

Вызов reverse() изменяет ваш массив на месте. Это создает трудноуловимые ошибки, которые сложно отлаживать.

Не путайте возвращаемые значения:

  • findLast() возвращает элемент или undefined
  • findLastIndex() возвращает индекс или -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() на разреженных массивах?

Да, findLast() работает с разреженными массивами. Он перебирает массив в обратном порядке и пропускает пустые ячейки, вызывая предикат только для элементов, которые фактически существуют. Это поведение аналогично тому, как find() обрабатывает разреженные массивы при прямом переборе.

Какая разница в производительности между findLast() и filter().pop()?

findLast() более эффективен, потому что он прекращает итерацию, как только находит совпадение. Использование filter().pop() сначала обрабатывает весь массив, создавая новый массив в памяти, а затем извлекает последний элемент. Для больших массивов findLast() обеспечивает значительный прирост производительности.

Работает ли findLast() с дженериками TypeScript?

Да, findLast() поддерживает дженерики TypeScript и сужение типов. Когда вы используете type guard в качестве предиката, TypeScript корректно выводит суженный тип для возвращаемого элемента. Убедитесь, что ваш tsconfig.json включает ES2023 в массиве lib для правильных определений типов.

Можно ли создать полифил для findLast() для старых браузеров?

Да, вы можете добавить полифил в Array.prototype, если метод не существует. Сначала проверьте наличие метода, затем назначьте функцию, которая перебирает массив в обратном порядке. Библиотеки Core-js и другие библиотеки полифилов также предоставляют готовые реализации для более широкой совместимости.

Open-source session replay

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.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.