JavaScript · 5장 자료구조와 자료형
배열 메서드 1: map · filter · find · some · every · includes
배열로 하는 일은 대부분 몇 가지 모양으로 정리돼요. 모두 바꾸기, 조건에 맞는 것만 고르기, 하나 찾기, 있는지 묻기예요. 이 모양마다 이름이 붙은 메서드가 있어서, for 문을 읽고 의도를 짐작할 필요 없이 이름만 보고 무슨 일을 하는지 알 수 있어요.
이 글의 예제는 모두 아래 상품 목록을 써요. 실행 블록마다 같은 목록을 넣어 두었어요.
const products = [
{ id: 1, name: '연필', price: 500, stock: 12 },
{ id: 2, name: '공책', price: 2000, stock: 0 },
{ id: 3, name: '지우개', price: 800, stock: 5 },
{ id: 4, name: '필통', price: 6500, stock: 0 },
{ id: 5, name: '형광펜', price: 1200, stock: 3 },
];
01. map: 모든 항목을 바꿔서 새 배열로
map(함수)은 항목마다 함수를 불러 돌려받은 값으로 새 배열을 만들어요. 결과의 길이는 원래 배열과 늘 같아요. 원래 배열은 바뀌지 않아요. 함수는 (항목, 번호, 배열)을 받아서, 번호가 필요하면 두 번째 인자를 써요.
const products = [
{ id: 1, name: '연필', price: 500, stock: 12 },
{ id: 2, name: '공책', price: 2000, stock: 0 },
{ id: 3, name: '지우개', price: 800, stock: 5 },
{ id: 4, name: '필통', price: 6500, stock: 0 },
{ id: 5, name: '형광펜', price: 1200, stock: 3 },
];
console.log(products.map(p => p.name)); // 이름만 모은 배열
console.log(products.map((p, i) => `${i + 1}. ${p.name}`)); // 번호 붙이기
console.log(products.map(p => { p.name })); // 중괄호를 쓰면?
마지막 줄은 undefined만 다섯 개예요. 화살표 함수에 중괄호를 쓰면 return을 직접 적어야 하는데 빠졌어요(화살표 함수). 객체를 돌려주고 싶을 때는 p => ({ ...p, sale: true })처럼 괄호로 감싸요. 그리고 map은 새 배열이 필요할 때만 써요. 결과를 쓰지 않고 항목마다 무언가를 하기만 할 거라면 forEach나 for...of가 맞아요.
02. filter와 find: 고르기와 하나 찾기
filter(함수)는 함수가 참을 돌려준 항목만 모아 새 배열로 줘요. 맞는 게 없으면 빈 배열 []이에요. find(함수)는 참이 되는 첫 번째 항목 하나를 주고, 없으면 undefined예요. 위치가 필요하면 findIndex(없으면 -1)를 써요.
const products = [
{ id: 1, name: '연필', price: 500, stock: 12 },
{ id: 2, name: '공책', price: 2000, stock: 0 },
{ id: 3, name: '지우개', price: 800, stock: 5 },
{ id: 4, name: '필통', price: 6500, stock: 0 },
{ id: 5, name: '형광펜', price: 1200, stock: 3 },
];
const inStock = products.filter(p => p.stock > 0);
console.log(inStock.length); // 3
console.log(products.filter(p => p.price > 10000)); // [] — 없으면 빈 배열
console.log(products.find(p => p.id === 3)); // 지우개 객체 하나
console.log(products.find(p => p.id === 9)); // undefined
console.log(products.find(p => p.id === 9)?.name ?? '없는 상품'); // 없는 상품
find는 못 찾으면 undefined라서, 결과에서 바로 속성을 읽으면 오류가 나요. 주소의 ID로 상품을 찾는 것처럼 없을 수 있는 경우엔 ?.와 ??를 붙이거나 먼저 확인해요(옵셔널 체이닝). "하나만 필요한데 filter를 쓰고 [0]을 읽는" 코드는 find로 바꾸면 의도가 더 분명하고, 찾은 뒤 바로 멈춰서 더 빨라요.
03. some · every · includes: 예, 아니오로 묻기
답이 참 · 거짓 하나면 되는 질문에는 이 셋을 써요.
some(함수)— 하나라도 참이면true. 찾는 순간 멈춰요.every(함수)— 모두 참이면true. 하나라도 거짓이면 멈춰요.includes(값)— 그 값이 들어 있는지. 함수가 아니라 값을 직접 넣어요.
const products = [
{ id: 1, name: '연필', price: 500, stock: 12 },
{ id: 2, name: '공책', price: 2000, stock: 0 },
{ id: 3, name: '지우개', price: 800, stock: 5 },
{ id: 4, name: '필통', price: 6500, stock: 0 },
{ id: 5, name: '형광펜', price: 1200, stock: 3 },
];
console.log(products.some(p => p.stock === 0)); // true — 품절이 하나라도 있나요?
console.log(products.every(p => p.price > 0)); // true — 모두 가격이 있나요?
console.log([].every(p => p.price > 100)); // true — 빈 배열은 늘 true
const tags = ['js', 'css'];
console.log(tags.includes('css')); // true
console.log([NaN].includes(NaN), [NaN].indexOf(NaN)); // true -1
console.log([{ id: 1 }].includes({ id: 1 })); // false — 객체는 같은 객체인지 봐요
console.log([{ id: 1 }].some(x => x.id === 1)); // true — 객체는 some으로
두 가지를 조심해요. every는 빈 배열에서 늘 true예요. "장바구니 상품이 모두 재고가 있으면 주문 가능"을 every로만 검사하면 빈 장바구니도 주문 가능이 돼요. 그리고 includes는 ===처럼 비교해서 객체는 내용이 같아도 못 찾아요. 객체 배열에서는 some으로 조건을 줘요.
04. 이어 쓰기, 그리고 반복문과 고르기
filter와 map은 새 배열을 돌려주니까 바로 이어서 쓸 수 있어요. "재고 있는 상품만 골라서 표시용 글자로 바꾸기"가 한 줄에 그대로 읽혀요.
const products = [
{ id: 1, name: '연필', price: 500, stock: 12 },
{ id: 2, name: '공책', price: 2000, stock: 0 },
{ id: 3, name: '지우개', price: 800, stock: 5 },
{ id: 4, name: '필통', price: 6500, stock: 0 },
{ id: 5, name: '형광펜', price: 1200, stock: 3 },
];
const labels = products
.filter(p => p.stock > 0)
.map(p => `${p.name} ${p.price}원`);
console.log(labels); // ['연필 500원', '지우개 800원', '형광펜 1200원']
그래도 반복문이 나은 때가 있어요. 조건에 맞으면 중간에 멈춰야 하거나(break), 한 번 돌면서 여러 결과를 같이 모으거나, 항목이 수십만 개라서 새 배열을 여러 번 만드는 비용이 신경 쓰일 때예요. 그 밖에는 메서드 이름이 의도를 말해 주는 쪽이 읽기 쉬워요.
// 흔한 실수: map에 함수를 그대로 넘기면 (항목, 번호, 배열)이 다 들어가요
console.log(['1', '2', '3'].map(parseInt)); // [1, NaN, NaN] — parseInt('2', 1)이 됐어요
console.log(['1', '2', '3'].map(Number)); // [1, 2, 3]
아래 코드는 반복문으로 세 가지를 구해요. 결과는 그대로 두고 filter · map · some · find로 바꿔 써 보세요. 각각 한 줄이면 돼요.
const orders = [
{ id: 101, item: '키보드', status: 'paid', amount: 59000 },
{ id: 102, item: '마우스', status: 'refund', amount: 25000 },
{ id: 103, item: '모니터', status: 'paid', amount: 289000 },
{ id: 104, item: '케이블', status: 'pending', amount: 9000 },
];
// 1. 결제 완료(paid)된 주문의 상품 이름
const paidItems = [];
for (const o of orders) {
if (o.status === 'paid') paidItems.push(o.item);
}
// 2. 환불(refund) 주문이 하나라도 있나요?
let hasRefund = false;
for (const o of orders) {
if (o.status === 'refund') { hasRefund = true; break; }
}
// 3. 주문 번호 103의 상품 이름
let item103;
for (const o of orders) {
if (o.id === 103) { item103 = o.item; break; }
}
console.log(paidItems, hasRefund, item103);
해답 보기
"골라서 바꾸기"는 filter + map, "하나라도 있나"는 some, "하나 찾기"는 find예요. find는 못 찾을 수도 있으니 ?.를 붙였어요. 결과는 ['키보드', '모니터'] true 모니터로 같아요.
const paidItems = orders.filter(o => o.status === 'paid').map(o => o.item);
const hasRefund = orders.some(o => o.status === 'refund');
const item103 = orders.find(o => o.id === 103)?.item;
05. 실전 포인트
- 언제 쓰나요? 목록 화면에 그릴 데이터 만들기(map), 검색 · 필터 결과(filter), ID로 상세 찾기(find), 버튼 활성화 조건(some · every), 선택 여부 확인(includes)에 써요.
- 대신 쓰는 것: 중간에 멈추거나 여러 결과를 한 번에 모으면
for...of, 합계 · 묶기는reduce(다음 글), ID로 아주 자주 찾는다면 배열 대신Map(5장)을 써요. - 흔한 실수: 화살표 함수 중괄호에서
return을 빠뜨리는 것, find 결과를 확인 없이 읽는 것, 빈 배열의 every가true인 걸 모르는 것, 객체를includes로 찾는 것,map(parseInt)예요.
정리
map은 같은 길이의 새 배열,filter는 고른 새 배열(없으면[])을 돌려줘요.find는 첫 항목 하나 또는undefined,findIndex는 위치 또는-1이에요.some·every·includes는 참 · 거짓으로 답해요. 빈 배열의 every는true예요.- filter → map처럼 이어 쓰면 의도가 그대로 읽혀요. 멈추거나 여러 결과를 모을 땐 반복문이 나아요.
상품이 하나도 없을 때 products.filter(…)와 products.find(…)는 각각 무엇을 돌려줄까요?
filter는 빈 배열 [], find는 undefined예요. find 결과에서 속성을 읽으려면 ?.를 붙이거나 먼저 확인해요.
"장바구니 상품이 모두 재고가 있으면 주문 버튼을 켠다"를 cart.every(…)로만 쓰면 어떤 문제가 생길까요?
빈 배열의 every는 늘 true라서, 장바구니가 비어 있어도 버튼이 켜져요. cart.length > 0 && cart.every(…)처럼 비어 있지 않은지도 함께 확인해요.
users.includes({ id: 1 })가 false인 이유와 대신 쓸 방법은?
includes는 ===처럼 같은 객체인지를 봐서, 내용이 같은 새 객체는 찾지 못해요. users.some(u => u.id === 1)처럼 조건으로 찾아요.