자바스크립트 배열에서 조건 검색: find()부터 findLast()까지
자바스크립트에서 배열을 다루다 보면 특정 조건에 맞는 원소를 하나만 찾아야 할 때가 종종 있는데요.
이럴 때 배열 전체를 filter()로 걸러낸 다음 첫 번째 원소만 꺼내는 코드를 작성하시는 분이 의외로 많습니다.
const users = [
{ name: "Alice", age: 28 },
{ name: "Bob", age: 35 },
{ name: "Charlie", age: 22 },
];
// 이렇게 하는 분들이 많은데...
const user = users.filter((u) => u.age > 30)[0];
물론 동작은 하지만, 배열 전체를 순회해서 새 배열을 만든 뒤 첫 번째 원소만 쓰고 나머지는 버리니까 비효율적이죠?
이번 글에서는 이런 상황에 딱 맞는 find() 계열 메서드 네 가지를 정리해보겠습니다.
find()로 원소 찾기
find() 메서드는 배열을 앞에서부터 순회하면서 콜백 함수가 true를 반환하는 첫 번째 원소를 돌려줍니다.
조건에 맞는 원소를 찾는 즉시 순회를 멈추니까 filter()보다 훨씬 낫죠.
const numbers = [10, 20, 30, 40, 50];
const found = numbers.find((num) => num > 25);
console.log(found); // 30
콜백 함수에는 세 가지 인자가 넘어오는데요.
array.find((element, index, array) => {
// element: 현재 원소
// index: 현재 인덱스
// array: find()를 호출한 원래 배열
});
보통은 첫 번째 인자만 쓰게 되지만 인덱스가 필요하면 두 번째 인자를 활용할 수도 있습니다.
조건을 만족하는 원소가 없으면 undefined를 반환해요.
const numbers = [10, 20, 30, 40, 50];
const found = numbers.find((num) => num > 100);
console.log(found); // undefined
그래서 find() 결과를 사용할 때는 undefined 여부를 확인하는 습관을 들이면 좋습니다.
const found = numbers.find((num) => num > 100);
if (found !== undefined) {
console.log(`찾았습니다: ${found}`);
} else {
console.log("조건에 맞는 원소가 없습니다");
}
객체 배열에서 find() 활용
실무에서는 단순한 숫자 배열보다 객체 배열을 다룰 일이 훨씬 많을 텐데요.
find()는 객체 배열에서 특정 조건에 맞는 원소를 찾을 때 진가를 발휘합니다.
const products = [
{ id: 1, name: "노트북", price: 1200000, inStock: true },
{ id: 2, name: "키보드", price: 89000, inStock: false },
{ id: 3, name: "마우스", price: 45000, inStock: true },
{ id: 4, name: "모니터", price: 350000, inStock: true },
];
ID로 특정 상품을 찾아볼까요?
const product = products.find((p) => p.id === 3);
console.log(product);
// { id: 3, name: "마우스", price: 45000, inStock: true }
여러 조건을 조합해서 검색할 수도 있어요.
const affordable = products.find((p) => p.price < 100000 && p.inStock);
console.log(affordable);
// { id: 3, name: "마우스", price: 45000, inStock: true }
참고로 filter()는 조건에 맞는 모든 원소를 배열로 반환하지만 find()는 가장 먼저 발견한 원소 하나만 반환합니다.
목적에 따라 골라 쓰면 돼요.
findIndex()로 위치 찾기
원소 자체가 아니라 원소의 인덱스가 필요한 경우도 있겠죠?
이때는 findIndex()를 쓰면 됩니다.
const numbers = [10, 20, 30, 40, 50];
const index = numbers.findIndex((num) => num > 25);
console.log(index); // 2
find()와 마찬가지로 앞에서부터 순회하면서 조건에 맞는 첫 번째 원소의 인덱스를 반환합니다.
다만 조건을 만족하는 원소가 없으면 undefined 대신 -1을 반환한다는 게 find()와 다른 점이에요.
const numbers = [10, 20, 30, 40, 50];
const index = numbers.findIndex((num) => num > 100);
console.log(index); // -1
findIndex()를 언제 쓰면 좋을지 좀 더 와닿는 예제를 볼까요?
배열에서 특정 원소를 찾아서 수정할 때 요긴하게 쓸 수 있습니다.
const tasks = [
{ id: 1, title: "장보기", done: false },
{ id: 2, title: "운동하기", done: false },
{ id: 3, title: "책 읽기", done: false },
];
const idx = tasks.findIndex((task) => task.id === 2);
if (idx !== -1) {
tasks[idx] = { ...tasks[idx], done: true };
}
console.log(tasks[1]);
// { id: 2, title: "운동하기", done: true }
indexOf()와의 차이
자바스크립트에는 비슷한 이름의 indexOf()라는 메서드도 있는데요.
findIndex()와 뭐가 다른지 헷갈리시는 분이 많을 겁니다.
indexOf()는 콜백 함수 대신 찾고자 하는 값 자체를 인자로 받습니다.
내부적으로 ===(엄격 비교)를 써서 값을 비교하죠.
const fruits = ["apple", "banana", "cherry"];
// indexOf: 값을 직접 비교
fruits.indexOf("banana"); // 1
// findIndex: 콜백 함수로 조건 지정
fruits.findIndex((f) => f === "banana"); // 1
단순히 특정 값의 위치를 찾을 때는 indexOf()가 간단하지만 복잡한 조건으로 검색해야 하면 findIndex()가 필요합니다.
const users = [
{ name: "Alice", age: 28 },
{ name: "Bob", age: 35 },
];
// indexOf로는 객체 배열 검색이 어렵습니다
users.indexOf({ name: "Alice", age: 28 }); // -1 (참조가 다르므로)
// findIndex를 사용해야 합니다
users.findIndex((u) => u.name === "Alice"); // 0
indexOf()는 ===로 비교하기 때문에 객체의 경우 같은 내용이라도 참조가 다르면 찾을 수 없다는 점, 기억해두세요.
findLast()로 뒤에서부터 찾기
지금까지 살펴본 find()와 findIndex()는 배열 앞에서부터 순회하면서 조건에 맞는 첫 번째 원소를 찾았는데요.
그런데 뒤에서부터 찾아야 할 때도 있지 않을까요?
예를 들어 트랜잭션 기록에서 가장 최근에 성공한 거래를 찾고 싶다면요?
const transactions = [
{ id: 1, amount: 100, status: "success" },
{ id: 2, amount: 200, status: "failed" },
{ id: 3, amount: 150, status: "success" },
{ id: 4, amount: 300, status: "failed" },
{ id: 5, amount: 250, status: "success" },
];
find()를 쓰면 가장 오래된 성공 거래(id: 1)가 반환될 겁니다.
가장 최근 거래를 찾으려면 배열을 뒤집어야 할까요?
// 배열을 뒤집어서 찾는 방법 (비효율적)
const last = [...transactions].reverse().find((t) => t.status === "success");
console.log(last);
// { id: 5, amount: 250, status: "success" }
배열을 복제하고 뒤집는 과정이 번거롭고 비효율적이죠.
ES2023에서 도입된 findLast()를 쓰면 깔끔하게 해결됩니다.
const last = transactions.findLast((t) => t.status === "success");
console.log(last);
// { id: 5, amount: 250, status: "success" }
findLast()는 find()와 사용법이 같지만 배열의 끝에서부터 앞으로 순회한다는 점만 다릅니다.
배열을 뒤집을 필요 없이 바로 마지막 일치 원소를 찾을 수 있어서 코드가 한결 깔끔해지죠.
findLastIndex()도 있습니다
findLast()가 find()의 역방향 버전이듯 findLastIndex()는 findIndex()의 역방향 버전입니다.
배열의 끝에서부터 순회하면서 조건에 맞는 첫 번째 원소의 인덱스를 반환하죠.
const scores = [72, 85, 90, 68, 95, 78, 88];
const lastHighIndex = scores.findLastIndex((s) => s >= 90);
console.log(lastHighIndex); // 4 (값: 95)
findIndex()와 마찬가지로 조건에 맞는 원소가 없으면 -1을 반환해요.
const scores = [72, 85, 68, 78];
const lastHighIndex = scores.findLastIndex((s) => s >= 90);
console.log(lastHighIndex); // -1
실전 활용 사례
findLast()와 findLastIndex()가 실제로 쓸 만한 상황을 몇 가지 더 살펴볼게요.
로그에서 마지막 오류 찾기
서버 로그나 이벤트 로그를 분석할 때 가장 최근 오류를 빠르게 확인하고 싶을 때가 있죠.
const logs = [
{ time: "09:00", level: "info", msg: "서버 시작" },
{ time: "09:15", level: "error", msg: "DB 연결 실패" },
{ time: "09:16", level: "info", msg: "DB 재연결 성공" },
{ time: "10:30", level: "error", msg: "API 타임아웃" },
{ time: "10:31", level: "info", msg: "API 재시도 성공" },
];
const lastError = logs.findLast((log) => log.level === "error");
console.log(lastError);
// { time: "10:30", level: "error", msg: "API 타임아웃" }
배열에서 마지막 짝수의 위치 찾기
findLastIndex()를 쓰면 특정 조건을 만족하는 마지막 원소의 위치를 바로 알아낼 수 있어요.
const numbers = [3, 6, 1, 4, 7, 2, 9];
const lastEvenIdx = numbers.findLastIndex((n) => n % 2 === 0);
console.log(lastEvenIdx); // 5 (값: 2)
문자열 배열에서 마지막 일치 항목 찾기
const tags = ["react", "hooks", "react", "state", "react", "memo"];
const lastReactIdx = tags.findLastIndex((tag) => tag === "react");
console.log(lastReactIdx); // 4
이 경우에는 lastIndexOf()를 써도 되는데요.
lastIndexOf()는 indexOf()처럼 값 자체를 비교하니까 단순 비교에는 간편하고, findLastIndex()는 콜백 함수로 복잡한 조건을 지정할 수 있어서 객체 배열이나 복잡한 조건에 잘 맞습니다.
// 단순 값 비교에는 lastIndexOf()가 간편
tags.lastIndexOf("react"); // 4
// 복잡한 조건에는 findLastIndex()
tags.findLastIndex((tag) => tag.startsWith("re")); // 4
네 가지 메서드 비교
지금까지 살펴본 네 가지 메서드를 표로 정리해봤습니다.
| 메서드 | 순회 방향 | 반환값 | 못 찾으면 |
|---|---|---|---|
find() | 앞 → 뒤 | 원소 | undefined |
findIndex() | 앞 → 뒤 | 인덱스 | -1 |
findLast() | 뒤 → 앞 | 원소 | undefined |
findLastIndex() | 뒤 → 앞 | 인덱스 | -1 |
find()와 findLast()는 원소를 반환하고 findIndex()와 findLastIndex()는 인덱스를 반환합니다.
순회 방향만 다를 뿐 사용법은 똑같으니 앞에서부터 찾을지 뒤에서부터 찾을지만 정하면 돼요.
타입스크립트에서 사용하기
타입스크립트에서 find() 계열 메서드를 쓸 때는 반환 타입을 좀 신경 써야 합니다.
find()와 findLast()는 원소를 못 찾을 수 있기 때문에 반환 타입이 T | undefined입니다.
그래서 반환값을 바로 사용하면 타입 오류가 나요.
interface User {
name: string;
age: number;
}
const users: User[] = [
{ name: "Alice", age: 28 },
{ name: "Bob", age: 35 },
];
const user = users.find((u) => u.age > 30);
// user의 타입: User | undefined
console.log(user.name);
// ^^^^ 오류: 'user'은(는) 'undefined'일 수 있습니다.
이런 오류를 피하려면 타입 가드를 쓰면 됩니다.
const user = users.find((u) => u.age > 30);
if (user) {
console.log(user.name); // OK, user의 타입이 User로 좁혀짐
}
Non-null assertion을 쓰는 방법도 있지만 값이 반드시 존재한다고 확신할 수 있을 때만 써야 해요.
// 값이 반드시 있다고 확신할 때만 사용
const user = users.find((u) => u.age > 30)!;
console.log(user.name);
브라우저 지원
find()와 findIndex()는 ES2015(ES6)에서 도입되어 오래전부터 모든 모던 브라우저에서 지원해왔습니다.
findLast()와 findLastIndex()는 ES2023에서 추가된 비교적 새로운 메서드인데요.
인터넷 익스플로러를 제외하면 모든 모던 브라우저에서 쓸 수 있습니다.
Node.js는 v18부터 지원하고 Deno와 Bun에서도 지원하니까 실무에서 안심하고 쓰셔도 됩니다.
마치며
이번 글에서는 자바스크립트 배열에서 조건에 맞는 원소를 찾는 네 가지 메서드를 살펴봤습니다.
find()와 findIndex()로 앞에서부터, findLast()와 findLastIndex()로 뒤에서부터 검색할 수 있죠.
배열을 뒤집거나 filter()로 전체를 걸러낸 뒤 하나만 꺼내는 것보다 목적에 맞는 메서드를 바로 쓰는 게 코드도 깔끔하고 성능도 좋으니까요.
자바스크립트의 배열을 다루는 다른 메서드가 궁금하시다면 배열 정렬 방법이나 reduce() 사용법도 참고해보세요.
This work is licensed under
CC BY 4.0