Menu

자바스크립트 고차 함수: map, filter, reduce와 콜백

자바스크립트 고차 함수를 제대로 이해해 봅시다. 함수를 인자로 넘기고, 함수를 반환하고, 실무에서 매일 쓰는 map·filter·reduce 패턴까지 정리했습니다.

함수는 값이다

자바스크립트에서 함수는 숫자나 문자열처럼 하나의 입니다. 변수에 담을 수도 있고, 배열에 넣을 수도 있으며, 다른 함수에 인자로 넘기거나 함수에서 반환할 수도 있죠. 이 단순한 사실 하나가 완전히 새로운 프로그래밍 스타일로 가는 문을 열어줍니다:

index.js
Output
Click Run to see the output here.

함수를 값처럼 다루는 데 익숙해지면, 고차 함수(higher order function)는 더 이상 어렵게 느껴지지 않습니다. _고차 함수_란 함수를 인자로 받거나, 함수를 반환하거나, 혹은 둘 다인 함수를 말합니다. 정의는 이게 전부예요.

함수를 인자로 넘기기

가장 흔한 형태는 콜백 함수를 받아서 대신 실행해 주는 함수입니다. 사실 이미 의식하지 않고도 여러 번 써 본 패턴이죠.

index.js
Output
Click Run to see the output here.

forEach도 고차 함수입니다. 함수를 인자로 받아서 각 요소마다 한 번씩 호출해 주죠. setTimeout 역시 마찬가지로, 전달한 함수를 일정 시간이 지난 뒤에 실행해 주는 고차 함수입니다. 개발자는 무엇을 할지에만 집중하면 되고, 언제 또는 몇 번 실행할지는 이 함수들이 알아서 처리해 줍니다.

직접 만드는 고차 함수도 구조는 똑같습니다. 조건이 참일 때만 콜백 함수를 실행하는 간단한 예제를 살펴볼까요?

index.js
Output
Click Run to see the output here.

action은 우연히 함수를 담고 있는 매개변수일 뿐입니다. action()으로 호출하면 전달받은 함수가 그대로 실행됩니다.

실무에서 진짜 쓰는 세 가지: map, filter, reduce

배열에는 웬만한 for 문을 대체할 수 있는 고차 함수 메서드들이 내장돼 있습니다. 이 세 가지만 제대로 익혀도 평소에 짜던 코드가 훨씬 짧고 명확해집니다.

map — 모든 요소를 변환하기

index.js
Output
Click Run to see the output here.

map은 배열의 각 요소마다 전달한 함수를 한 번씩 호출해서, 반환값들을 모아 새로운 배열을 만들어 줍니다. 길이는 그대로이고 내용만 변환된 형태죠. 원본 배열은 건드리지 않습니다.

filter — 조건에 맞는 값만 골라내기

index.js
Output
Click Run to see the output here.

filter는 콜백이 truthy를 반환한 항목만 남기고 나머지는 걸러냅니다. 반환값은 새 배열이며, 원본보다 짧아질 수 있습니다.

reduce — 배열을 하나의 값으로 접기

index.js
Output
Click Run to see the output here.

reduce는 그중에서도 가장 범용적인 녀석입니다. 콜백이 누적값(accumulator)과 현재 값을 받고, 여기서 반환하는 값이 다음 번 누적값이 되는 구조죠. 두 번째 인자(여기서는 0)는 시작값입니다.

이런 메서드들은 체이닝해서 쓸 수 있습니다. 바로 이 지점에서 함수형 스타일의 진가가 드러나죠:

index.js
Output
Click Run to see the output here.

위에서 아래로 그냥 읽으면 됩니다. 결제 완료된 주문만 걸러내고, 가격만 뽑아낸 다음, 전부 더한다. 반복문도 없고, 값을 바꿔가며 쌓아두는 카운터 변수도 없고, 인덱스를 하나 놓쳤네 마네 할 일도 없습니다.

함수를 반환하는 함수

고차 함수의 또 다른 축입니다. 함수를 만들어서 돌려주는 함수죠:

index.js
Output
Click Run to see the output here.

multiplyBy(2)를 한 번 호출하면 완전히 새로운 함수가 리턴됩니다. 그리고 이 새 함수는 여전히 factor 값을 기억하고 있죠 — 이게 바로 클로저 인데, 자세한 내용은 뒤에서 따로 다룹니다. 지금은 이 정도만 기억하면 됩니다. multiplyBy에 어떤 인자를 넘기느냐에 따라, 같은 틀에서 찍어낸 서로 다른 특화 함수들을 얻을 수 있다는 거예요.

이 패턴은 정말 여기저기서 자주 등장합니다:

index.js
Output
Click Run to see the output here.

정의 한 번으로 재사용 가능한 함수 두 개를 뽑아냈습니다. warninfo를 각각 손으로 작성하고 변경될 때마다 동기화하는 것보다 훨씬 낫죠.

이름 붙인 함수 vs 인라인 콜백 함수

인라인 화살표 함수를 그대로 넘겨도 되고, 이름으로 정의한 함수를 넘겨도 됩니다. 둘 다 잘 동작하니 읽기 편한 쪽을 고르면 됩니다:

index.js
Output
Click Run to see the output here.

isEven괄호 없이 넘기면 함수 자체가 전달됩니다. 여기에 ()를 붙이면 함수가 바로 실행돼서 그 _결과값_이 넘어가 버리죠. 초보자들이 자주 하는 실수입니다:

nums.filter(isEven);     // 올바름: 함수를 전달
nums.filter(isEven());   // 잘못됨: 인자 없이 isEven을 호출하여 그 결과를 전달

콜백이 두세 줄을 넘어가기 시작하면, 밖으로 빼서 이름을 붙여주세요. 그렇게 하면 주변 코드의 가독성이 훨씬 좋아집니다.

실전 예제로 살펴보기

고차 함수는 작은 조각들을 조합할 때 진가를 발휘합니다. 예를 들어, 상품 목록에서 재고가 있고 가격이 적당한 상품들의 이름만 뽑아서 대문자로 바꿔야 한다고 해봅시다:

index.js
Output
Click Run to see the output here.

헬퍼 함수는 각자 한 가지 일만 합니다. 배열 메서드도 각자 한 가지 변환만 수행하죠. 파이프라인을 따라 읽다 보면 "어떻게 반복할지"가 아니라 "무엇을 원하는지"에 대한 명세처럼 읽힙니다.

고차 함수가 어울리지 않는 경우

자바스크립트 고차 함수는 강력하지만, 모든 반복문을 대체하는 만능 도구는 아닙니다.

  • 중간에 반복을 멈춰야 한다면, forEach에서 억지로 빠져나오려 애쓰는 것보다 forfor...ofbreak를 쓰는 편이 훨씬 명확합니다.
  • 콜백 안에서 비동기 작업을 한다면 map이나 forEachawait를 기다려 주지 않습니다. for...ofawait를 함께 쓰거나, mapPromise.all을 조합하세요.
  • 콜백이 공유 상태를 변경하고 있다면, 이 스타일의 장점에서 멀어지고 있는 겁니다. 일반 반복문을 쓰거나, 새 값을 반환하도록 리팩터링하는 게 낫습니다.

상황에 맞게만 쓴다면 map, filter, reduce는 일상적인 코드에서 반복문 보일러플레이트를 대부분 걷어내 줍니다. 반대로 아무 데나 남발하면 오히려 가독성을 해칩니다. 의도를 가장 또렷하게 드러내는 도구를 고르세요.

다음: 객체

값으로 다룰 만한 건 함수만 있는 게 아닙니다. 객체는 관련된 데이터와 동작을 한데 묶는, 자바스크립트의 주력 도구예요. 방금 filter하고 map했던 배열들도 대부분 객체로 채워져 있었죠. 다음 페이지에서 다뤄보겠습니다.

자주 묻는 질문

자바스크립트에서 고차 함수(higher-order function)란 무엇인가요?

다른 함수를 인자로 받거나, 함수를 결과로 반환하는 함수를 고차 함수라고 합니다. 둘 중 하나만 해당해도 고차 함수예요. Array.prototype.map, setTimeout, addEventListener가 대표적인 예인데, 모두 콜백을 받아서 대신 실행해 줍니다.

map, filter, reduce는 각각 어떻게 다른가요?

map은 각 요소를 변환해서 길이가 같은 새 배열을 돌려줍니다. filter는 콜백이 참을 반환한 요소만 남겨서 (보통 더 짧은) 배열을 반환하죠. reduce는 요소를 하나씩 합쳐 나가며 배열을 하나의 값으로 접어버리는 역할을 합니다. 셋 다 콜백을 받는다는 점에서 모두 고차 함수입니다.

함수에서 함수를 반환하는 이유가 뭔가요?

같은 로직을 반복하지 않고, 설정값만 바꾼 작은 헬퍼 함수를 만들기 위해서예요. 예를 들어 multiplyBy(n)n을 곱하는 새 함수를 반환하기 때문에, multiplyBy(2)multiplyBy(10)만으로 서로 다른 두 개의 전용 함수를 얻을 수 있습니다. 이 패턴은 클로저를 활용한 것으로, 이벤트 핸들러, 미들웨어, 유틸리티 라이브러리에서 정말 자주 등장합니다.

Coddy로 코딩 배우기

시작하기