앞에서 for, while, for...of 같은 반복문들로
"어떻게 도는지"를 직접 제어하는 방법을 봤다.
이번에는 한 단계만 더 올라가서,
배열에서 정말 자주 보게 되는 고차 함수 4종 세트를 정리해보려고 한다.
- forEach
- map
- filter
- reduce
이 네 개만 제대로 익혀도,
데이터를 순회하고 가공하는 코드가 훨씬 깔끔해진다.
결론 먼저
- forEach: 단순히 "각 요소에 대해 어떤 작업을 한다" → 결과를 모으지 않음
- map: 각 요소를 변환해서 새 배열을 만든다
- filter: 조건을 만족하는 요소만 골라서 새 배열을 만든다
- reduce: 배열 전체를 하나의 값으로 축약한다 (합계, 평균, 객체로 묶기 등)
아주 짧게 요약하면 나는 이렇게 쓴다
- 부수 효과(로그 찍기, DOM 조작 등)만 필요 → forEach
- 배열을 다른 배열로 변환 → map
- 배열에서 일부만 걸러내기 → filter
- 배열 → 숫자/문자열/객체 등 단일 값으로 만들기 → reduce
왜 굳이 고차 함수냐?
"그냥 for로 돌면 되는 거 아닌가?" 라는 생각이 들 수 있다.
물론 가능하다. 실제로도 for만으로 대부분 할 수 있다.
하지만 고차 함수들은 의도를 코드에 그대로 드러내기 쉽다는 장점이 있다. -> 빠르게 읽고 해석할 수 있음
예를 들어, 배열을 2배로 만드는 코드를 보자.
const nums = [1, 2, 3];
// for 버전
const doubled1 = [];
for (let i = 0; i < nums.length; i++) {
doubled1.push(nums[i] * 2);
}
// map 버전
const doubled2 = nums.map(n => n * 2);
둘 다 결과는 같다. 하지만 읽을 때 느낌은 다르다.
- for 버전: "i를 0부터 length까지 돌리고, 각 요소를 꺼내서 2배를 해서 새 배열에 push한다"
- map 버전: "이 배열을 변환해서 새 배열을 만든다"
이게 고차 함수의 핵심이다.
"어떻게 도는가"보다 "무엇을 하고 싶은가"를 더 잘 보여줄 수 있다고 생각한다.
1. forEach – 단순 순회 + 부수 효과
forEach는 배열의 각 요소에 대해 콜백 함수를 실행한다.
const nums = [1, 2, 3];
nums.forEach((value, index) => {
console.log(index, value);
});
// 0 1
// 1 2
// 2 3
콜백의 기본 형태는
arr.forEach((value, index, array) => {
// value: 현재 요소
// index: 현재 인덱스
// array: 원본 배열
});
중요한 포인트
- forEach는 항상 undefined를 반환한다
- 즉, forEach 자체로 새 배열을 만들 수 없다
const nums = [1, 2, 3];
const result = nums.forEach(n => n * 2);
console.log(result); // undefined
그래서 forEach는 주로
- 로그 찍기
- DOM 조작
- 외부 변수에 값 쌓기
등 부수 효과(side effect)가 필요할 때 쓴다.
2. map – 배열을 변환해서 새 배열 만들기
map은 배열의 각 요소를 변환(transform) 해서 새 배열을 만든다.
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2);
console.log(doubled);
// [2, 4, 6]
console.log(nums);
// [1, 2, 3] (원본 유지)
기본 형태는 forEach와 비슷하다.
const newArray = arr.map((value, index, array) => {
return /* 새 값 */;
});
- 콜백이 무엇을 반환하느냐에 따라 새 배열의 요소가 결정된다
- 원본 배열은 바뀌지 않고, 항상 같은 길이의 새 배열이 반환된다
예시: 유저 객체 배열에서 이름만 뽑기
const users = [
{ id: 1, name: 'Lee' },
{ id: 2, name: 'Kim' },
];
const names = users.map(user => user.name);
console.log(names);
// ['Lee', 'Kim']
언제 map?
- 배열을 그대로 돌리되, 형태만 바꾸고 싶을 때
- 예: 숫자 → 문자열, 객체 → 다른 형태의 객체, 등
3. filter – 조건을 만족하는 것만 추리기
filter는 말 그대로 걸러내는 함수다.
- 콜백에서 true를 반환한 요소만 새 배열에 남는다
- 원본 배열은 변경되지 않는다
const nums = [1, 2, 3, 4, 5];
const even = nums.filter(n => n % 2 === 0);
console.log(even);
// [2, 4]
기본 형태는
const filtered = arr.filter((value, index, array) => {
return /* 조건: true/false */;
});
예시: 활성 유저만 고르기
const users = [
{ id: 1, name: 'Lee', active: true },
{ id: 2, name: 'Kim', active: false },
{ id: 3, name: 'Park', active: true },
];
const activeUsers = users.filter(user => user.active);
console.log(activeUsers);
// [ { id: 1, ... }, { id: 3, ... } ]
언제 filter?
- 배열에서 일부만 골라내고 싶을 때
- 예: 특정 조건의 유저, 특정 타입의 값, 특정 상태의 아이템 등
4. reduce – 배열 전체를 하나의 값으로 축약하기
reduce는 고차 함수 중에서 가장 헷갈리지만,
익숙해지면 가장 강력한 도구다.
기본 개념은 이렇다.
배열의 모든 요소를 왼쪽에서 오른쪽으로 돌면서, 하나의 값으로 "축약"한다.
기본 형태
const result = arr.reduce((accumulator, currentValue, index, array) => {
// accumulator: 누적 값
// currentValue: 현재 요소
return /* 다음 누적 값 */;
}, initialValue);
예시 1) 합계 구하기
const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, n) => {
return acc + n;
}, 0);
console.log(sum);
// 10
동작 흐름은 이렇게 된다
- 초기 acc = 0
- 1번째 요소: acc = 0 + 1 → 1
- 2번째 요소: acc = 1 + 2 → 3
- 3번째 요소: acc = 3 + 3 → 6
- 4번째 요소: acc = 6 + 4 → 10
예시 2) 객체로 묶기
const users = [
{ id: 1, name: 'Lee' },
{ id: 2, name: 'Kim' },
{ id: 3, name: 'Park' },
];
const userMap = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
console.log(userMap);
// {
// 1: { id: 1, name: 'Lee' },
// 2: { id: 2, name: 'Kim' },
// 3: { id: 3, name: 'Park' },
// }
이렇게 reduce는 숫자 합계뿐 아니라,
- 객체로 인덱싱하기
- 그룹핑하기
- 통계 값(최대/최소/평균) 구하기
같은 작업에도 많이 쓰인다.
언제 reduce?
- 배열을 돌면서 어떤 누적 결과 하나를 만들어야 할 때
- 그 결과가 숫자, 문자열, 객체, 배열 등 어떤 형태든 상관없다
네 가지를 한 번에 비교해 보기
같은 배열에 대해, 4가지 함수를 각자 어떻게 쓰는지 비교해 보면 느낌이 더 잘 온다.
const nums = [1, 2, 3, 4];
// 1) forEach: 그냥 돌면서 로그만 찍기
nums.forEach(n => {
console.log('값:', n);
});
// 2) map: 모든 값에 2배 해서 새 배열 만들기
const doubled = nums.map(n => n * 2);
// [2, 4, 6, 8]
// 3) filter: 짝수만 뽑기
const even = nums.filter(n => n % 2 === 0);
// [2, 4]
// 4) reduce: 합계 구하기
const sum = nums.reduce((acc, n) => acc + n, 0);
// 10
요약하면
- forEach: "그냥 한 바퀴 돌면서 ~~한다"
- map: "각 요소를 ~~로 바꿔서 새 배열 만든다"
- filter: "조건 만족하는 것만 남겨서 새 배열 만든다"
- reduce: "전체를 하나의 결과로 만든다"
for vs forEach vs map vs filter vs reduce 선택 기준
실제 코드 짤 때, 머릿속에서 이렇게 고르면 편하다.
- 반복이 필요하다 → 근데 결과값을 따로 만들 필요는 없다
- 로그 찍기, DOM 조작, 외부 상태 업데이트
- → forEach 또는 그냥 for
- 배열을 -> 다른 배열로 바꾸고 싶다
- 길이는 같고, 값만 바뀌는 경우 → map
- 일부만 남기고 싶다 → filter
- 배열 전체에서 숫자/문자열/객체 하나를 뽑아내고 싶다
- 합계, 평균, 카운트, 맵/그룹, 통계 등 → reduce
- 성능이 극단적으로 민감한 구간이다
- → 가장 단순한 for 문이 유리한 경우도 있다 (특히 큰 배열 + 빈번한 호출)
- 다만 일반적인 서비스 코드에서는 가독성 > 미세한 성능 차이인 경우가 훨씬 많다
오늘 내용 한 줄씩 다시 정리
- forEach: 부수 효과용. 새 배열을 만들고 싶을 땐 쓰지 않는다.
- map: "배열을 변환해서 새 배열" 만드는 도구.
- filter: "조건을 만족하는 요소만 남겨서 새 배열" 만드는 도구.
- reduce: "배열 전체를 하나의 값으로 축약"하는 도구.
- 고차 함수는 결국, 빠르게 읽고 해석할 수 있음
'FrontEnd > JavaScript' 카테고리의 다른 글
| JavaScript 배열 고차 함수 - some, every, find, findIndex (조건 탐색 편) (0) | 2025.12.10 |
|---|---|
| JavaScript 컬렉션 - Set (0) | 2025.12.10 |
| JavaScript 기본기 정리 – 반복문·배열·문자열·Math 한 장에 모으기 (0) | 2025.11.04 |
| JavaScript 컬렉션 - Array (0) | 2025.09.09 |
| JavaScript 컬렉션 - Map (0) | 2025.09.09 |