1. 고차함수
개념
고차함수는 함수를 인자로 받거나, 함수를 반환하는 함수임.
JavaSCript에서 함수는 "일급 객체"이기 때문에 변수에 담거나, 다른 함수에 전달하거나 반환할 수 있음
즉, 보통 함수는 숫자나 문자열 같은 "값"을 받지만, 고차함수는 "함수 자체"를 값처럼 주고 받는 함수임
대표적인 고차함수들
1) forEach - 배열 순회
const fruits = ['사과', '바나나', '오렌지'];
fruits.forEach(function(fruit, index) {
console.log(`${index}: ${fruit}`);
});
// 화살표 함수로 더 간단하게
fruits.forEach((fruit, index) => {
console.log(`${index}: ${fruit}`);
});
// 출력:
// 0: 사과
// 1: 바나나
// 2: 오렌지
동작 설명
- `forEach`는 배열의 요소를 처음부터 끝까지 하나씩 순회함
- 콜백 함수에 전달되는 인자: 첫 번째는 현재 요(`fruit`), 두 번째는 인덱스(`index`)
- 1회차: `fruit = 사과`, `index = 0` -> "0:사과" 출력
- 2회차: `fruit = 바나나`, `index = 1` -> "1:바나나" 출력
- 3회차: `fruit = 오렌지`, `index = 2` -> "2:오렌지" 출력
- 반환값이 없음 (undefined) - 단순히 실행만 하고 끝
요즘은 화살표 함수를 더 자주 쓰기 때문에 화살표 함수로 줄여쓰는 습관을 들여보자
2) map - 배열 변환 (새 배열 반환)
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] - 원본은 그대로!
동작 설명
- `map`은 배열의 각 요소에 함수를 적용하고, 그 결과들을 모아 새 배열을 반환함
- 콜백 함수가 `return`하는 값이 새 배열의 요소가 됨
- 1회차: `num = 1` -> `1 * 2 = 2` 반환
- 2회차: `num = 2` -> `2 * 2 = 4` 반환
- ... 5회차 : `num = 5` -> `5 * 2 = 10` 반환
- 최종적으로 `[2, 4, 6, 8, 10]` 이라는 새 배열이 만들어짐
- 원본 배열 `numbers`는 변하지 않음!!
forEach와 map 차이
- `forEach`: 반환값 없음, 단순히 각 요소에 대해 작업 수행
- `map` : 새 배열 반환, 변환된 결과를 담은 배열이 필요할 때 수행
// 실제로 많이 쓰는 패턴: 객체 배열에서 특정 속성만 추출
const users = [
{ id: 1, name: '김철수', age: 25 },
{ id: 2, name: '이영희', age: 30 },
{ id: 3, name: '박민수', age: 28 }
];
const userNames = users.map(user => user.name);
console.log(userNames); // ['김철수', '이영희', '박민수']
동작 설명
- 각 `user`객체에서 `name` 속성만 꺼내서 반환
// 객체를 다른 형태의 객체로 변환
const userCards = users.map(user => {
return {
displayName: `${user.name} (${user.age}세)`,
isAdult: user.age >= 18
};
});
console.log(userCards);
// [
// { displayName: '김철수 (25세)', isAdult: true },
// { displayName: '이영희 (30세)', isAdult: true },
// { displayName: '박민수 (28세)', isAdult: true }
// ]
동작 설명:
- 원본 객체 구조와 완전히 다른 새로운 객체를 만들어 반환
- 각 `user`에서 필요한 정보를 조합해서 새로운 형태로 가공
- React에서 API 응답 데이터를 화면에 맞게 변환할 때 자주 사용하는 패턴
3) filter - 조건에 맞는 요소만 추출
목적: 배열에서 특정 조건을 만족하는 요소들만 골라서 새 배열을 만들고 싶을 때 사용
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - 원본 그대로
동작 설명:
- `filter`는 콜백 함수가 `true`를 반환하는 요소만 모아서 새 배열을 만듭니다
- `num % 2 === 0`: 2로 나눈 나머지가 0이면 짝수
- 1회차: `num = 1` -> `1 % 2 = 1` -> `false` -> 제외
- 2회차: `num = 2` -> `2 % 2 = 0 `-> `true` -> 포함
- 3회차: `num = 3` -> `3 % 2 = 0 `-> `false` -> 제외
- 4회차: `num = 4` -> `4 % 2 = 0 `-> `true` -> 포함
- ... (이하 동일한 방식으로 진행)
- 결과: `true`를 반환한 `[2, 4, 6, 8, 10]`만 남음
// 실용 예시: 특정 조건으로 데이터 필터링
const people = [
{ name: '김철수', age: 25 },
{ name: '이영희', age: 17 },
{ name: '박민수', age: 30 },
{ name: '최지원', age: 15 }
];
// 성인만 필터링 (18세 이상)
const adults = people.filter(person => person.age >= 18);
console.log(adults);
// [
// { name: '김철수', age: 25 },
// { name: '박민수', age: 30 }
// ]
- 각 `person` 객체의 `age`가 18 이상인지 검사
- 1회차: 김철수(25세) -> `25 >= 18` -> `true` -> 포함
- 2회차: 김철수(17세) -> `17 >= 18` -> `false` -> 제외
- 3회차: 김철수(30세) -> `30 >= 18` -> `true` -> 포함
- 4회차: 김철수(15세) -> `15 >= 18` -> `false` -> 제외
- 결과: 성인인 김철수, 박민수만 남은 배열
// 검색 기능 구현 예시
const products = ['아이폰', '아이패드', '맥북', '애플워치', '삼성 갤럭시'];
const searchKeyword = '아이';
const searchResults = products.filter(product => product.includes(searchKeyword));
console.log(searchResults); // ['아이폰', '아이패드']
동작 설명:
- `includes()`: 문자열에 특정 문자가 포함되어 있는지 확인 (있으면 true)
- '아이폰'.includes('아이') → `true` → 포함
- '아이패드'.includes('아이') -> `true` -> 포함
- '맥북'.includes('아이') -> `false` -> 제외
- 실제 검색 기능 구현할 때 이런 패턴을 많이 사용
4) reduce - 배열을 하나의 값으로 축소
목적: 배열의 모든 요소를 순회하면서 하나의 결과값으로 합치고 싶을 때 사용
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, current) => {
return accumulator + current;
}, 0);
console.log(sum); // 15
```
**동작 설명**:
- `reduce`는 두 개의 인자를 받습니다: 콜백 함수, 초기값(여기서는 0)
- `accumulator`: 누적값 (이전 단계의 결과)
- `current`: 현재 순회 중인 요소
- 초기값 0에서 시작해서 각 요소를 더해나감
**단계별 진행**:
```
1단계: accumulator = 0 (초기값), current = 1
→ return 0 + 1 = 1
2단계: accumulator = 1 (이전 결과), current = 2
→ return 1 + 2 = 3
3단계: accumulator = 3, current = 3
→ return 3 + 3 = 6
4단계: accumulator = 6, current = 4
→ return 6 + 4 = 10
5단계: accumulator = 10, current = 5
→ return 10 + 5 = 15
최종 결과: 15
// 실용 예시: 장바구니 총액 계산
const cart = [
{ name: '사과', price: 3000, quantity: 2 },
{ name: '바나나', price: 2000, quantity: 3 },
{ name: '오렌지', price: 2500, quantity: 1 }
];
const totalPrice = cart.reduce((total, item) => {
return total + (item.price * item.quantity);
}, 0);
console.log(totalPrice); // 14500
동작 설명:
- 각 상품의 (가격 × 수량)을 누적해서 더함
- 1단계: `total = 0`, 사과 → `0 + (3000 × 2) = 6000`
- 2단계: `total = 6000`, 바나나 → `6000 + (2000 × 3) = 12000`
- 3단계: `total = 12000`, 오렌지 → `12000 + (2500 × 1) = 14500`
- 최종: 14500원
// 배열을 객체로 변환하는 예시
const fruits = ['사과', '바나나', '사과', '오렌지', '바나나', '사과'];
const fruitCount = fruits.reduce((count, fruit) => {
count[fruit] = (count[fruit] || 0) + 1;
return count;
}, {});
console.log(fruitCount); // { 사과: 3, 바나나: 2, 오렌지: 1 }
동작 설명:
- 초기값은 빈 객체 `{}`
- `count[fruit] || 0`: count 객체에 해당 과일이 없으면 0, 있으면 기존 값
- 각 과일을 만날 때마다 해당 과일의 카운트를 1씩 증가
- 1단계: `count = {}`, '사과' → `{ 사과: 1 }`
- 2단계: `count = { 사과: 1 }`, '바나나' → `{ 사과: 1, 바나나: 1 }`
- 3단계: '사과' → `{ 사과: 2, 바나나: 1 }`
- ... 반복
- 이런 패턴은 데이터 집계할 때 유용
1-5) find / findIndex - 조건에 맞는 첫 번째 요소 찾기
목적: 배열에서 조건을 만족하는 첫 번째 요소를 찾고 싶을 때 사용
const users = [
{ id: 1, name: '김철수' },
{ id: 2, name: '이영희' },
{ id: 3, name: '박민수' }
];
// find: 조건에 맞는 첫 번째 "요소" 반환
const user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: '이영희' }
// findIndex: 조건에 맞는 첫 번째 "인덱스" 반환
const index = users.findIndex(u => u.id === 2);
console.log(index); // 1
동작 설명 (find):
- 배열을 순회하면서 조건을 만족하는 요소를 찾으면 즉시 반환하고 종료
- 1회차: `u = { id: 1, name: '김철수' }` → `1 === 2` → `false` → 다음으로
- 2회차: `u = { id: 2, name: '이영희' }` → `2 === 2` → `true` → 이 요소 반환, 종료
- 3회차는 실행되지 않음 (이미 찾았으므로)
filter와의 차이:
- `filter`: 조건 맞는 모든 요소를 배열로 반환
- `find`: 조건 맞는 첫 번째 요소 하나만 반환
- 하나만 찾으면 되는 경우 `find`가 더 효율적
// 못 찾은 경우
const notFound = users.find(u => u.id === 999);
console.log(notFound); // undefined
const notFoundIndex = users.findIndex(u => u.id === 999);
console.log(notFoundIndex); // -1
설명:
- 조건에 맞는 요소가 없으면 `find`는 `undefined` 반환
- `findIndex`는 `-1` 반환
- 실제 코드에서는 이 경우를 처리해줘야 함
1-6) some / every - 조건 만족 여부 확인
목적: 배열의 요소들이 조건을 만족하는지 불리언(true/false)으로 확인하고 싶을 때
const numbers = [1, 2, 3, 4, 5];
// some: 하나라도 조건을 만족하면 true
const hasEven = numbers.some(num => num % 2 === 0);
console.log(hasEven); // true (2, 4가 짝수이므로)
// every: 모든 요소가 조건을 만족해야 true
const allPositive = numbers.every(num => num > 0);
console.log(allPositive); // true (모두 양수)
const allEven = numbers.every(num => num % 2 === 0);
console.log(allEven); // false (홀수도 있으므로)
동작 설명 (some):
- 하나라도 `true`를 반환하면 즉시 `true` 반환하고 종료
- 1회차: `1 % 2 === 0` → `false` → 계속
- 2회차: `2 % 2 === 0` → `true` → 즉시 true 반환, 종료
- 나머지는 확인하지 않음 (효율적)
동작 설명 (every):
- 하나라도 `false`를 반환하면 즉시 `false` 반환하고 종료
- 모두 `true`여야 최종 `true`
// 실용 예시: 유효성 검사
const formFields = [
{ name: 'email', value: 'test@test.com', valid: true },
{ name: 'password', value: '1234', valid: false },
{ name: 'name', value: '홍길동', valid: true }
];
const isFormValid = formFields.every(field => field.valid);
console.log(isFormValid); // false (password가 invalid)
const hasError = formFields.some(field => !field.valid);
console.log(hasError); // true (password에 에러 있음)
1-7) 메서드 체이닝 - 여러 고차함수 연결
목적: 여러 고차함수를 연결해서 복잡한 데이터 처리를 깔끔하게 표현
const products = [
{ name: '노트북', price: 1500000, inStock: true },
{ name: '마우스', price: 50000, inStock: true },
{ name: '키보드', price: 100000, inStock: false },
{ name: '모니터', price: 300000, inStock: true }
];
// 재고 있는 상품 중 가격이 10만원 이상인 것의 이름만 추출
const result = products
.filter(product => product.inStock) // 1단계: 재고 있는 것만
.filter(product => product.price >= 100000) // 2단계: 10만원 이상만
.map(product => product.name); // 3단계: 이름만 추출
console.log(result); // ['노트북', '모니터']
```
**단계별 동작 설명**:
```
원본 데이터:
[노트북(150만,재고O), 마우스(5만,재고O), 키보드(10만,재고X), 모니터(30만,재고O)]
1단계 filter (inStock === true):
→ [노트북(150만), 마우스(5만), 모니터(30만)]
(키보드는 재고 없어서 제외됨)
2단계 filter (price >= 100000):
→ [노트북(150만), 모니터(30만)]
(마우스는 5만원이라 제외됨)
3단계 map (name만 추출):
→ ['노트북', '모니터']
왜 체이닝을 사용함?
- 각 단계가 명확하게 분리되어 읽기 쉬움
- 각 단계마다 새 배열이 반환되므로 다음 메서드 연결 가능
- 복잡한 데이터 처리를 선언적으로 표현할 수 있음
// 체이닝 없이 같은 작업을 하면...
const inStockProducts = products.filter(p => p.inStock);
const expensiveProducts = inStockProducts.filter(p => p.price >= 100000);
const productNames = expensiveProducts.map(p => p.name);
// 변수가 많아지고 번거로움
```
---
'React > JavaScript 심화' 카테고리의 다른 글
| [JavaScript 심화] 비동기 - async/await (0) | 2025.11.11 |
|---|---|
| [JavaScript 심화] 비동기 - Promise (0) | 2025.11.11 |
| [JavaScript 심화] 비동기 - 콜백 함수 (0) | 2025.11.11 |
| [JavaScript 심화] 동기와 비동기 (0) | 2025.11.11 |
| [JavaScript 심화] Date 객체 (0) | 2025.11.11 |