Menu

자바스크립트 배열 메서드: map, filter, reduce 총정리

for 루프 대신 쓰게 되는 배열 메서드들 — map, filter, reduce, find, some, every를 정리하고, 원본을 바꾸는 메서드와 새 배열을 반환하는 메서드를 구분해 봅니다.

배열에는 강력한 도구 상자가 딸려 온다

자바스크립트 배열에는 기본으로 내장된 메서드가 꽤 많다. for 루프로 짜던 대부분의 작업들 — 값을 변환하거나, 조건에 맞는 항목만 골라내거나, 전부 합산하는 등 — 은 이미 한 줄짜리 메서드로 준비되어 있고, 읽기도 훨씬 좋고, 서로 자연스럽게 이어 쓸 수 있다.

가장 먼저 익혀야 할 자바스크립트 배열 메서드는 세 가지다. map, filter, 그리고 reduce. 이 셋과 몇 가지 친척 메서드만 손에 익히면, 루프로 뒤덮여 있던 코드가 한눈에 들어오는 간결한 형태로 줄어든다.

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

각 메서드는 콜백을 받아서 무언가를 반환합니다. 그리고 어느 것도 nums를 직접 바꾸지 않았다는 점 — 이건 일찌감치 머리에 새겨둘 만한 포인트입니다.

map: 모든 요소를 변환하기

map은 함수를 받아서 각 요소마다 호출하고, 그 반환값을 모아 원본과 길이가 같은 새 배열로 만들어 줍니다. "입력 하나당 출력 하나"가 필요할 때 쓰면 딱 맞습니다.

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

콜백의 두 번째 인자로 인덱스도 받을 수 있습니다: arr.map((item, i) => ...). 필요 없으면 그냥 무시하면 됩니다.

자주 하는 실수 하나. 반환된 배열이 필요 없는데도 map을 쓰는 경우입니다. 각 요소를 단순히 출력하거나 DB에 넣기만 한다면 forEach나 일반 반복문이 맞습니다.

filter: 조건에 맞는 요소만 걸러내기

filter는 각 요소에 대해 true/false를 반환하는 판별 함수(predicate)를 실행하고, 결과가 truthy인 요소만 남깁니다. 새로 만들어지는 배열의 길이는 원본과 같거나 더 짧습니다.

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

mapfilter는 자연스럽게 체이닝됩니다. 체인은 앞에서 뒤로 하나의 파이프라인처럼 읽어 내려가면 됩니다.

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

먼저 filter로 거를 것만 남기고, 그다음에 map을 돌리세요. 그래야 map이 살아남은 원소들만 처리합니다.

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

reduce는 셋 중에서 가장 범용적인 메서드입니다. (accumulator, item) => newAccumulator 형태의 리듀서 함수와 초기값을 넘기면, 배열을 순회하면서 각 원소를 지금까지의 누적값과 함께 리듀서에 전달하고, 최종적으로 누적된 값을 반환합니다.

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

결과가 꼭 숫자일 필요는 없습니다. 객체든, 또 다른 배열이든, 문자열이든 — 누적해서 만들 수 있는 건 뭐든 반환값이 될 수 있죠:

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

초기값(두 번째 인자)은 항상 넘기는 습관을 들이세요. 생략하면 reduce는 배열의 첫 번째 요소를 누적값의 시작점으로 써버리는데, 빈 배열에서 에러가 나는 건 물론이고 애초에 원하는 동작이 아닐 때도 많습니다.

js reduce는 강력하지만, 로직이 조금만 꼬여도 가독성이 뚝 떨어집니다. 리듀서 함수가 몇 줄을 넘어간다면 그냥 평범한 for...of 루프로 풀어 쓰는 편이 훨씬 읽기 좋습니다.

forEach: 반환값 없이 부수 효과만 일으키기

forEach는 한마디로 "새 배열을 돌려주지 않는 map"이라고 보면 됩니다. 각 요소로 뭔가를 실행 하고 싶을 때 — 로그를 찍거나, API를 호출하거나, DOM을 업데이트할 때처럼 — 새 컬렉션이 필요 없는 상황에 쓰는 메서드죠.

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

두 가지만 기억해 두세요.

  • forEachundefined를 반환합니다. 그래서 뒤에 .map()을 체이닝할 수 없어요.
  • forEach 중간에 break로 빠져나올 수도 없습니다. 조기 종료가 필요하다면 for...ofsome/every를 쓰세요.

혹시 arr.forEach(x => results.push(transform(x))) 같은 코드를 쓰고 있다면, 그건 사실 map으로 써야 할 상황입니다.

find와 findIndex: 딱 하나만 찾고 싶을 때

find는 조건을 만족하는 첫 번째 요소를 돌려주고, 없으면 undefined를 반환합니다. findIndex는 해당 요소의 인덱스를, 찾지 못하면 -1을 반환합니다.

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

find는 첫 번째로 매칭되는 요소를 찾으면 거기서 멈춥니다. filter(...)[0] 같은 식으로 쓰지 마세요. 어차피 버릴 나머지까지 배열 전체를 훑게 되니까요.

some과 every: 배열에 대한 참/거짓 질문

some은 조건을 통과하는 요소가 하나라도 있으면 true를 돌려줍니다. 반면 every는 모든 요소가 조건을 통과해야만 true가 됩니다.

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

둘 다 단락 평가(short-circuit)로 동작합니다. some은 처음으로 true가 나오는 순간 멈추고, every는 처음으로 false가 나오는 순간 멈춰요. "하나라도 ~인가?" / "전부 ~인가?" 같은 질문에 딱 맞는 도구죠.

slice와 splice 차이: 복사냐, 잘라내기냐

이름이 비슷해서 헷갈리지만, 하는 일은 전혀 다릅니다.

slice(start, end)는 배열의 일부를 얕은 복사해서 돌려줍니다. 원본은 건드리지 않아요. end는 포함되지 않으며, 생략하면 끝까지 복사합니다.

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

splice(start, deleteCount, ...items)는 원본 배열을 직접 수정하는 메서드입니다. start 위치에서부터 deleteCount개의 요소를 제거하고, 필요하다면 그 자리에 새 요소를 끼워 넣은 뒤, 제거된 요소들을 반환합니다.

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

기억하기 쉬운 팁: slice는 안전하게(복사), splice는 원본을 직접 수술한다고 외우면 헷갈리지 않는다.

원본을 바꾸는 메서드 vs 바꾸지 않는 메서드

이 구분은 꽤 중요하다. 공유되는 배열을 실수로 변경해 버려서 생기는 버그는 추적하기가 유독 까다로운 편이다.

원본을 바꾸는(mutating) 메서드 — 원본 배열을 직접 수정하고, 반환값은 보통 따로 있다:

  • push, pop, shift, unshift
  • splice, sort, reverse
  • fill, copyWithin

원본을 바꾸지 않는(non-mutating) 메서드 — 새 배열이나 값을 반환하고 원본은 그대로 둔다:

  • map, filter, slice, concat
  • flat, flatMap
  • find, findIndex, some, every, includes, indexOf
  • reduce, reduceRight

특히 조심해야 할 녀석은 sortreverse다. 이름만 보면 순해 보이는데 조용히 원본을 바꿔 버린다. 정렬된 복사본이 필요하다면 먼저 slice로 복사해 두고 쓰자:

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

최신 자바스크립트에는 원본을 건드리지 않는 쌍둥이 메서드들도 준비돼 있습니다. 바로 toSorted, toReversed, toSpliced, with인데요, 원본 배열은 그대로 두고 새 배열을 반환합니다. 요즘 런타임이라면 어디서든 쓸 수 있으니, 가능하면 이쪽을 활용하세요.

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

flat과 flatMap

flat은 중첩된 배열을 한 단계 펼쳐 줍니다. depth 인자를 넘기면 더 깊은 단계까지 펼칠 수 있고요. flatMap은 이름 그대로 map 다음에 한 단계짜리 flat을 붙인 형태라, 하나의 요소가 0개 이상의 결과로 확장될 때 특히 잘 맞습니다.

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

flatMap은 "입력 하나에 출력 여러 개"로 항목을 펼칠 때 flat()을 따로 붙이지 않고도 깔끔하게 처리할 수 있는 메서드입니다.

실전 예제로 조합해 보기

좀 더 현실적인 예제를 하나 살펴볼게요. 주문 목록이 주어졌을 때, 완료된 주문 중 $50을 넘는 것들의 총 매출을 구해봅시다:

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

메서드 세 개로 이어지는 파이프라인, 루프 변수 관리 같은 번거로움은 없습니다. 각 단계가 무슨 일을 하는지 그대로 드러나죠. 두 개의 filter를 하나로 합칠 수도 있지만, 이렇게 나눠 두면 읽기도 편하고 디버깅할 때도 도움이 될 때가 있습니다.

다음 주제: Map과 Set

배열은 순서가 있는 데이터를 다루는 데는 잘 맞지만, 키로 빠르게 값을 찾거나 중복 없는 값만 모으려고 하면 다루기가 영 까다롭습니다. 자바스크립트에는 바로 이런 상황을 위한 내장 자료구조인 MapSet이 있고, 다음 페이지에서 다룹니다.

자주 묻는 질문

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

map은 각 요소를 변환해서 길이가 같은 새 배열을 돌려줍니다. filter는 조건을 통과한 요소만 남긴 (보통 더 짧은) 새 배열을 반환하고요. reduce는 배열을 훑으면서 하나의 값으로 접어내는 역할입니다. 합계든, 객체든, 또 다른 배열이든 원하는 형태로 쌓아 올릴 수 있어요.

forEach와 map은 뭐가 다른가요?

forEach는 각 요소에 대해 함수를 실행하고 undefined를 반환합니다. 즉, 사이드 이펙트용이에요. 반면 map은 함수를 실행한 결과를 모아 새 배열로 돌려줍니다. 변환된 배열이 필요하면 map, 결과값 없이 요소마다 뭔가만 하고 싶다면 forEachfor...of 루프를 쓰면 됩니다.

원본 배열을 변경하는 메서드는 어떤 게 있나요?

원본을 바꾸는(mutating) 메서드는 push, pop, shift, unshift, splice, sort, reverse, fill, copyWithin입니다. 그 외 map, filter, slice, concat, flat, flatMap, find, some, every, reduce는 원본을 건드리지 않고 새 값을 반환해요.

slice와 splice는 언제 어떤 걸 써야 하나요?

slice(start, end)는 배열의 일부를 얕게 복사해서 돌려주며, 원본은 그대로 둡니다. 반면 splice(start, deleteCount, ...items)는 원본을 직접 수정해요. 지정한 위치에서 요소를 지우거나 끼워넣고, 제거된 요소를 반환합니다. 외우는 팁: slice는 안전하게 잘라 가고, splice는 원본을 직접 수술한다고 기억하세요.

Coddy로 코딩 배우기

시작하기