JavaScript 배열 메서드: map filter forEach reduce 차이

··설명 약 7분·작성: 해비·블로그 소개

JavaScript의 map, filter, forEach, reduce는 어떤 결과를 반환하나요?

map은 변환한 새 배열, filter는 조건을 통과한 새 배열, reduce는 누적 결과 하나를 만듭니다. forEach는 항목 처리를 실행하며 undefined를 반환합니다. 필요한 결과 형태로 메서드를 고릅니다.

공식 문서 참고: MDN: 배열 순회·변환·선택·누적 메서드

혼자 실행해 보는 배열 메서드

JavaScript 배열 메서드: map filter forEach reduce 차이 · 독립 실습

배열을 map·filter·find로 처리한 결과와 원본 배열을 비교하세요.

예제 크게 보기 (새 탭)

독자 질문: 네 메서드는 같은 목록에서 무엇을 돌려주며 원본을 바꾸는가?

js-04-example.mjs로 저장하고 node js-04-example.mjs를 실행하세요. 외부 패키지는 필요하지 않습니다. 브라우저 Console에도 전체 코드를 붙여 넣을 수 있습니다.

const tasks = [
  { id: '1', title: 'JavaScript 학습', completed: false },
  { id: '2', title: '화면 확인', completed: true },
  { id: '3', title: '문제 풀기', completed: false },
];
const pending = tasks.filter((task) => !task.completed);
const labels = pending.map((task) => `[미완료] ${task.title}`);
const eachResult = labels.forEach((label) => console.log(label));
const completedCount = tasks.reduce((count, task) => count + Number(task.completed), 0);
console.log('forEach 반환값:', eachResult);
console.log('완료 개수:', completedCount);
console.log('원본 개수:', tasks.length);

예상 출력:

[미완료] JavaScript 학습
[미완료] 문제 풀기
forEach 반환값: undefined
완료 개수: 1
원본 개수: 3

filter와 map은 새 배열을 만들고 forEach는 undefined를 반환합니다. reduce는 초기값 0부터 완료 수를 누적합니다. 이 예제의 Node.js 실행 결과는 위 출력과 일치했습니다. 브라우저 Console 실행은 미확인입니다.

직접 풀어볼 문제

완료 작업만 선택해 제목 배열을 출력해 보세요. 예상 배열은 ['화면 확인']입니다. filter와 map의 순서를 설명한 뒤 tasks[0] === pending[0]의 결과도 예측해 보세요.

forEach: 각 요소마다 작업 실행

const numbers = [1, 2, 3];


const result = numbers.forEach((number) => {
  console.log(number);
});


console.log(result);

forEach()의 반환값은 undefined입니다. 따라서 새 배열을 얻기 위한 도구로 사용하지 않습니다.

로그 출력, DOM 반영처럼 각 요소마다 부수효과를 실행할 때 사용할 수 있습니다.

map: 각 요소를 변환한 새 배열

const numbers = [1, 2, 3];
const doubled = numbers.map((number) => number * 2);


console.log(doubled);

결과는 [2, 4, 6]이며 원본 numbers는 그대로입니다.

filter: 조건을 통과한 요소만 선택

const numbers = [1, 2, 3, 4];
const evenNumbers = numbers.filter((number) => number % 2 === 0);


console.log(evenNumbers);

콜백이 true를 반환한 요소만 새 배열에 들어갑니다.

reduce: 배열을 하나의 누적 결과로 만들기

const numbers = [1, 2, 3];


const total = numbers.reduce((sum, number) => {
  return sum + number;
}, 0);


console.log(total);

reduce는 숫자 합계뿐 아니라 객체, Map, 배열 등 하나의 누적 결과를 만들 수 있습니다. 다만 읽기 어려운 한 줄 코드가 되기 쉽기 때문에 단순 변환은 map, 단순 선택은 filter가 더 명확합니다.

선택 기준

각 요소마다 실행만 필요 → forEach
모든 요소를 변환 → map
일부 요소만 선택 → filter
여러 요소를 하나의 결과로 누적 → reduce
map은 변환, filter는 선택, forEach는 각 항목 처리, reduce는 누적 결과 계산에 사용합니다.
개념 설명용 일러스트. map은 변환, filter는 선택, forEach는 각 항목 처리, reduce는 누적 결과 계산에 사용합니다. 이미지 원본 열기

원본 배열은 자동으로 안전해지는 것이 아닙니다

map이나 filter가 새 배열을 반환해도 배열 안 객체는 같은 참조일 수 있습니다.

const tasks = [{ id: '1', completed: false }];
const copied = tasks.map((task) => task);


console.log(tasks === copied);
console.log(tasks[0] === copied[0]);

첫 결과는 false지만 두 번째는 true입니다. 객체 참조와 불변 갱신은 다음 회차에서 이어집니다.

체이닝은 목적이 보일 때만

const tasksForTitles = [{ title: '학습', completed: false }, { title: '확인', completed: true }];
const titles = tasksForTitles
  .filter((task) => !task.completed)
  .map((task) => task.title);

‘미완료 작업을 고르고 제목만 만든다’는 흐름이 명확합니다. 여러 단계가 길어지면 중간 변수로 나누는 편이 디버깅하기 쉽습니다.

자주 발생하는 오류

map에서 return을 빠뜨립니다

const result = [1, 2].map((number) => {
  number * 2;
});

중괄호를 사용하면 return을 직접 써야 합니다.

forEach 결과를 배열로 생각합니다

forEach()는 새 배열을 반환하지 않습니다.

filter에서 값을 반환합니다

filter 콜백은 유지 여부를 나타내는 boolean 의미로 작성하는 편이 읽기 쉽습니다.

학습 완료 체크리스트

  • 네 메서드의 반환값 차이를 설명할 수 있습니다.
  • 변환·선택·누적·부수효과를 구분할 수 있습니다.
  • 메서드 체이닝을 읽을 수 있습니다.
  • 새 배열과 내부 객체 참조의 차이를 이해했습니다.

독립 실습은 여기서 마칠 수 있습니다. 아래 선택 실습은 TaskFlow 프로젝트 파일로 진행합니다.

선택 실습: 할 일 앱에 적용하기

TaskFlow는 작업을 추가·완료·삭제하는 할 일 앱입니다. 이 선택 실습에서는 JavaScript로 화면에 동작을 붙입니다.

deleteTask와 filterTasks를 배열 메서드로 바꾸고 필터링 결과를 확인합니다.

선택: 연재 프로젝트와 전체 자료

연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.

TaskFlow 프로젝트 적용

JavaScript 배열 메서드: map filter forEach reduce 차이 · 단계별 TaskFlow

TaskFlow JavaScript 4단계입니다. 기존 서비스 화면과 실제 콘솔 출력을 확인하세요.

예제 크게 보기 (새 탭)

변경 설명과 부분 예제를 읽은 뒤, 시작·완료 파일과 적용 순서에서 필요한 선언·import·화면 연결을 포함한 전체 파일을 확인하세요. 이번 단계의 변경 이유와 적용 위치도 함께 확인합니다.

시작본과 이번 변경

JavaScript 함수와 객체, import export로 모듈 나누기의 완료 체크포인트를 시작본으로 사용합니다.

js/task-store.js의 deleteTask·filterTasks를 배열 메서드로 교체합니다. js/app.js는 아래 예제처럼 미완료 목록의 제목을 출력하도록 바꿉니다. 초기 데이터의 미완료 제목 두 개와 완료 필터 한 개를 비교한 뒤 다음 글의 불변 갱신으로 이어갑니다.

이전 체크포인트

javascript-step-03-functions-modules

함수 이름은 유지하고 구현만 배열 메서드 중심으로 정리합니다.

이번 회차 변경 코드와 전체본 연결

HTML/CSS와 모듈 구조는 이전 체크포인트를 유지합니다. 아래 코드는 이번 체크포인트의 최종 JavaScript 파일입니다.

전체 파일·코드 보기

js/task-store.js

let tasks = [
  { id: '1', title: 'JavaScript 과정 정리', completed: false },
  { id: '2', title: 'TaskFlow 화면 확인', completed: true },
  { id: '3', title: '다음 글 읽기', completed: false },
];


export function getTasks() {
  return tasks;
}


export function addTask(title) {
  const task = {
    id: String(Date.now()),
    title,
    completed: false,
  };


  tasks.push(task);
  return task;
}


export function toggleTask(id) {
  const task = tasks.find((item) => item.id === id);


  if (!task) {
    return null;
  }


  task.completed = !task.completed;
  return task;
}


export function deleteTask(id) {
  tasks = tasks.filter((task) => task.id !== id);
}


export function filterTasks(status = 'all') {
  if (status === 'completed') {
    return tasks.filter((task) => task.completed);
  }


  if (status === 'pending') {
    return tasks.filter((task) => !task.completed);
  }


  return [...tasks];
}

js/app.js

import { filterTasks } from './task-store.js';


const pendingTasks = filterTasks('pending');
const labels = pendingTasks.map((task) => `[미완료] ${task.title}`);


labels.forEach((label) => {
  console.log(label);
});

검증

미완료 작업의 제목만 Console에 출력되고 filterTasks('completed')를 호출하면 완료 작업만 반환되는지 확인합니다.

이번 회차에서 reduce는 역할 비교까지만 합니다. 실제 TaskFlow 요약 계산은 6회차에서 작성합니다.

중간부터 시작하는 전체 파일과 적용 순서

이번 글 시작 ZIP · 이번 글 완료 ZIP

시작 ZIP은 직전 프로젝트 단계 javascript-step-03-functions-modules의 실제 완료 파일입니다. 프로젝트 순서를 이어가는 기준이며 사이트의 기술 읽기 순서와는 구분합니다.

  1. 시작 ZIP을 새 폴더에 풉니다. 이 폴더가 작업 루트이며 기존 체크포인트 파일을 별도로 찾을 필요가 없습니다.
  2. task-store.js와 app.js를 배열 메서드 버전으로 교체합니다.
  3. 아래 변경 파일을 같은 상대 경로에 추가하거나 전체 교체합니다. 표시하지 않은 파일과 images는 시작본 그대로 유지합니다. package-lock.json은 완료 ZIP에 있는 파일을 함께 적용합니다.
  4. 작업 루트에서 명령을 실행하고 예상 결과를 확인합니다. 완료 ZIP은 직접 적용한 결과를 비교하는 기준입니다.

추가·교체 파일

  • CHANGES.md — 전체 교체
  • README.md — 전체 교체
  • js/app.js — 전체 교체
  • js/task-store.js — 전체 교체

실행과 확인

npm start
npm test

개발 서버는 실행 상태로 두고 다른 터미널에서 검사 명령을 실행하세요. Node.js 24.18.0에서 검증했습니다. JavaScript는 http://127.0.0.1:4173, TypeScript는 Vite 실행 출력의 주소로 접속합니다.

예상 결과: 미완료 제목 2개, completed 필터 1개를 확인합니다.

실제 검증: JavaScript 모듈·DOM·경계값 실행 검사 통과. 시작 ZIP에 이 글의 변경 파일만 적용한 결과와 완료 ZIP의 모든 파일 내용이 일치했습니다. 브라우저 화면 배치·실제 기기·외부 CDN 가용성은 이 검사에 포함하지 않았습니다.

이 절의 전체 파일은 ZIP과 일치합니다. 위쪽 원문의 짧은 예제나 생략 표시가 있는 부분 코드는 개념·변경 위치 설명용입니다. 서버 저장과 인증은 제공하지 않으며 JSON fixture는 로컬 읽기 실습용입니다.

복사할 변경 파일 전체 코드

js/app.js — 전체 파일

import { filterTasks } from './task-store.js';


const pendingTasks = filterTasks('pending');
const labels = pendingTasks.map((task) => `[미완료] ${task.title}`);


labels.forEach((label) => {
  console.log(label);
});

js/task-store.js — 전체 파일

let tasks = [
  { id: '1', title: 'JavaScript 과정 정리', completed: false },
  { id: '2', title: 'TaskFlow 화면 확인', completed: true },
  { id: '3', title: '다음 글 읽기', completed: false },
];


export function getTasks() {
  return tasks;
}


export function addTask(title) {
  const task = {
    id: crypto.randomUUID(),
    title,
    completed: false,
  };


  tasks.push(task);
  return task;
}


export function toggleTask(id) {
  const task = tasks.find((item) => item.id === id);


  if (!task) {
    return null;
  }


  task.completed = !task.completed;
  return task;
}


export function deleteTask(id) {
  tasks = tasks.filter((task) => task.id !== id);
}


export function filterTasks(status = 'all') {
  if (status === 'completed') {
    return tasks.filter((task) => task.completed);
  }


  if (status === 'pending') {
    return tasks.filter((task) => !task.completed);
  }


  return [...tasks];
}

완료 체크포인트

javascript-step-04-array-methods

다음 학습

다음 글에서는 지금 toggleTask()가 원본 객체를 직접 바꾸는 문제를 재현하고 불변 갱신 방식으로 수정합니다.

다음 글: JavaScript 객체 참조와 불변 갱신: 중첩 객체를 안전하게 바꾸기

글 소개·학습 준비·전체 목차
배열 메서드: 변환과 선택

JavaScript · 4 / 15 · 필수 학습 · map filter forEach reduce

map은 변환한 새 배열, filter는 조건을 통과한 요소의 새 배열, reduce는 누적 결과 하나를 반환합니다. forEach는 각 요소의 처리를 실행하며 반환값은 undefined입니다.

이번 글에서는 forEach, map, filter, reduce를 같은 배열에 적용해 차이를 비교합니다.

이번 글에서 배우는 내용

  • forEach의 반환값
  • map으로 새 배열 만들기
  • filter로 일부 요소 선택하기
  • reduce로 하나의 결과 누적하기
  • 원본 배열 변경 여부
  • 메서드 체이닝 기준

먼저 알아야 할 내용

함수와 객체, 콜백의 기본 개념을 알고 있으면 됩니다.

목차

이 글이 도움이 되었나요?

조회 중

읽음 기록

읽음 — / 전체 15개 · 필수 14개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기