3.3 배열 고차 함수
고차 함수란?
**고차 함수(Higher-Order Function)**는 함수를 인수로 받거나 함수를 반환하는 함수입니다. JavaScript 배열의 고차 함수들은 명령형 루프를 함수형 스타일로 대체하여 코드를 더 간결하고 읽기 쉽게 만들어줍니다.
map — 변환
각 요소를 변환하여 새 배열 반환. 원본 배열은 변경되지 않습니다.
const numbers = [1, 2, 3, 4, 5];
// 각 요소를 2배로
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] (원본 변경 없음)
// 객체 배열 변환
const users = [
{ id: 1, name: "김철수", age: 25 },
{ id: 2, name: "이영희", age: 30 },
{ id: 3, name: "박민수", age: 22 },
];
// 이름만 추출
const names = users.map(user => user.name);
console.log(names); // ["김철수", "이영희", "박민수"]
// 구조 변환
const userCards = users.map(({ id, name }) => ({
key: id,
label: name,
}));
// 인덱스도 사용 가능
const indexed = numbers.map((n, i) => `${i}: ${n}`);
console.log(indexed); // ["0: 1", "1: 2", ...]
// 실전: API 응답 변환
const apiUsers = [
{ user_id: 1, display_name: "alice", email_address: "alice@example.com" },
{ user_id: 2, display_name: "bob", email_address: "bob@example.com" },
];
const normalizedUsers = apiUsers.map(user => ({
id: user.user_id,
name: user.display_name,
email: user.email_address,
}));
filter — 조건 필터링
조건을 만족하는 요소만 모아 새 배열 반환.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 짝수만
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
// 홀수만
const odds = numbers.filter(n => n % 2 !== 0);
console.log(odds); // [1, 3, 5, 7, 9]
// 객체 배열 필터링
const products = [
{ name: "노트북", price: 1200000, inStock: true },
{ name: "마우스", price: 35000, inStock: false },
{ name: "키보드", price: 85000, inStock: true },
{ name: "모니터", price: 450000, inStock: true },
{ name: "헤드셋", price: 120000, inStock: false },
];
// 재고 있는 상품만
const available = products.filter(p => p.inStock);
// 10만원 이하 상품만
const affordable = products.filter(p => p.price <= 100000);
// 복합 조건
const bestDeals = products.filter(p => p.inStock && p.price <= 100000);
console.log(bestDeals.map(p => p.name)); // ["마우스" 제외, "키보드"]
// 실전: 빈 값 제거
const mixed = ["a", null, "b", undefined, "", "c", false];
const truthy = mixed.filter(Boolean); // Falsy 값 모두 제거
console.log(truthy); // ["a", "b", "c"]
reduce — 누산기
배열을 하나의 값으로 축약합니다.
const numbers = [1, 2, 3, 4, 5];
// 합계
const sum = numbers.reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 15
// 콜백(accumulator, currentValue, currentIndex, array)
const product = numbers.reduce((acc, cur) => acc * cur, 1);
console.log(product); // 120
// 최대값 (Math.max 대신)
const max = numbers.reduce((max, cur) => cur > max ? cur : max, -Infinity);
console.log(max); // 5
// 배열로 그룹화
const words = ["apple", "banana", "avocado", "blueberry", "cherry"];
const grouped = words.reduce((acc, word) => {
const letter = word[0]; // 첫 글자
acc[letter] ??= []; // 없으면 빈 배열 생성
acc[letter].push(word);
return acc;
}, {});
console.log(grouped);
// {
// a: ["apple", "avocado"],
// b: ["banana", "blueberry"],
// c: ["cherry"]
// }
// 객체 배열에서 합산
const orders = [
{ product: "노트북", quantity: 1, price: 1200000 },
{ product: "마우스", quantity: 2, price: 35000 },
{ product: "키보드", quantity: 1, price: 85000 },
];
const totalAmount = orders.reduce((sum, order) => {
return sum + (order.quantity * order.price);
}, 0);
console.log(totalAmount); // 1355000
// 중복 제거 (Set보다 객체 배열에 유용)
const items = [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
{ id: 1, name: "A" }, // 중복
];
const unique = items.reduce((acc, item) => {
if (!acc.find(i => i.id === item.id)) {
acc.push(item);
}
return acc;
}, []);
find와 findIndex
조건을 만족하는 첫 번째 요소(또는 인덱스)를 반환합니다.
const users = [
{ id: 1, name: "김철수", role: "user" },
{ id: 2, name: "이영희", role: "admin" },
{ id: 3, name: "박민수", role: "user" },
];
// find: 첫 번째 일치 요소 반환 (없으면 undefined)
const admin = users.find(u => u.role === "admin");
console.log(admin); // { id: 2, name: "이영희", role: "admin" }
const notFound = users.find(u => u.role === "superadmin");
console.log(notFound); // undefined
// findIndex: 인덱스 반환 (없으면 -1)
const adminIndex = users.findIndex(u => u.role === "admin");
console.log(adminIndex); // 1
// findLast / findLastIndex (ES2023): 뒤에서부터 탐색
const numbers = [1, 3, 5, 2, 4, 6, 3, 7];
const lastOdd = numbers.findLast(n => n % 2 !== 0);
console.log(lastOdd); // 7
some과 every
조건 검사 결과를 boolean으로 반환합니다.
const numbers = [1, 2, 3, 4, 5];
// some: 하나라도 조건 만족하면 true
console.log(numbers.some(n => n > 4)); // true (5가 있음)
console.log(numbers.some(n => n > 10)); // false
// every: 모두 조건 만족해야 true
console.log(numbers.every(n => n > 0)); // true (모두 양수)
console.log(numbers.every(n => n > 2)); // false (1, 2가 포함)
// 실전: 유효성 검사
const formData = {
fields: [
{ name: "이름", value: "김철수", required: true },
{ name: "이메일", value: "test@test.com", required: true },
{ name: "주소", value: "", required: false },
],
};
const isValid = formData.fields
.filter(f => f.required)
.every(f => f.value.trim() !== "");
console.log(isValid); // true (필수 필드들이 모두 채워짐)
// includes: 특정 값 포함 여부
const roles = ["admin", "user", "moderator"];
console.log(roles.includes("admin")); // true
console.log(roles.includes("superuser")); // false
flat과 flatMap
중첩 배열을 평탄화합니다.
// flat: 중첩 배열 펼치기
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]] (기본: 1단계만)
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]
console.log(nested.flat(Infinity)); // 모두 평탄화
// flatMap: map 후 flat(1)
const sentences = ["Hello World", "JavaScript is fun"];
const words = sentences.flatMap(s => s.split(" "));
console.log(words); // ["Hello", "World", "JavaScript", "is", "fun"]
// vs map + flat
const same = sentences.map(s => s.split(" ")).flat();
// flatMap이 더 효율적
// 실전: 조건부 요소 추가
const items = [1, 2, 3, 4, 5];
const processed = items.flatMap(n => n % 2 === 0 ? [n, n * 2] : [n]);
console.log(processed); // [1, 2, 4, 3, 4, 8, 5]
메서드 체이닝
여러 배열 메서드를 연결하여 데이터를 단계적으로 처리합니다.
const transactions = [
{ id: 1, type: "income", amount: 500000, category: "salary" },
{ id: 2, type: "expense", amount: 45000, category: "food" },
{ id: 3, type: "income", amount: 200000, category: "freelance" },
{ id: 4, type: "expense", amount: 120000, category: "transport" },
{ id: 5, type: "expense", amount: 35000, category: "food" },
{ id: 6, type: "income", amount: 80000, category: "bonus" },
];
// 지출 항목의 총합 계산
const totalExpense = transactions
.filter(t => t.type === "expense") // 지출만 필터
.map(t => t.amount) // 금액만 추출
.reduce((sum, amount) => sum + amount, 0); // 합산
console.log(totalExpense); // 200000
// 카테고리별 지출 그룹화
const expenseByCategory = transactions
.filter(t => t.type === "expense")
.reduce((acc, t) => {
acc[t.category] = (acc[t.category] || 0) + t.amount;
return acc;
}, {});
console.log(expenseByCategory);
// { food: 80000, transport: 120000 }
고수 팁
reduce로 map과 filter 구현하기
// reduce로 map 구현
const myMap = (arr, fn) => arr.reduce((acc, item) => [...acc, fn(item)], []);
// reduce로 filter 구현
const myFilter = (arr, fn) => arr.reduce((acc, item) => fn(item) ? [...acc, item] : acc, []);
// reduce로 한 번에 map + filter (성능상 유리)
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 짝수만 2배로 (filter + map을 한 번의 순회로)
const result = numbers.reduce((acc, n) => {
if (n % 2 === 0) acc.push(n * 2);
return acc;
}, []);
console.log(result); // [4, 8, 12, 16, 20]
// filter().map() 은 두 번 순회하지만 reduce는 한 번
최신 배열 메서드 (ES2023+)
const arr = [1, 2, 3, 4, 5];
// toSorted: sort의 불변 버전 (원본 변경 없음)
const sorted = arr.toSorted((a, b) => b - a); // 내림차순
console.log(sorted); // [5, 4, 3, 2, 1]
console.log(arr); // [1, 2, 3, 4, 5] (원본 유지)
// toReversed: reverse의 불변 버전
const reversed = arr.toReversed();
console.log(reversed); // [5, 4, 3, 2, 1]
console.log(arr); // [1, 2, 3, 4, 5]
// toSpliced: splice의 불변 버전
const spliced = arr.toSpliced(2, 1, 99); // 인덱스 2부터 1개 제거, 99 삽입
console.log(spliced); // [1, 2, 99, 4, 5]
// with: 특정 인덱스 값 교체 (불변)
const updated = arr.with(2, 30); // 인덱스 2를 30으로
console.log(updated); // [1, 2, 30, 4, 5]