JavaScript 배열 필수 메서드 완벽 가이드

map, filter, reduce, splice의 개념부터 실전 활용까지

JavaScript 배열 필수 메서드 완벽 가이드

현대 JavaScript 개발에서 배열은 가장 많이 사용되는 데이터 구조 중 하나입니다. 특히 map, filter, reduce, splice와 같은 메서드들은 배열을 효과적으로 다루기 위한 필수 도구입니다. 이 글에서는 각 메서드의 기본 개념부터 실전 활용까지 자세히 알아보겠습니다.

학습 목표

이 글을 통해 다음과 같은 내용을 배울 수 있습니다:

  • 각 메서드의 기본 개념과 사용법
  • 불변성(Immutability)의 개념과 중요성
  • 실제 프로젝트에서의 활용 사례
  • 메서드 간의 차이점과 선택 기준

1. map - 변환의 마법사

map 메서드는 배열의 각 요소를 변환하여 새로운 배열을 생성합니다. 원본 배열은 변경되지 않으며, 항상 같은 길이의 새로운 배열을 반환합니다.

기본 구문

javascript
const newArray = array.map((element, index, array) => {
  // 변환 로직
  return transformedElement;
});

실전 예제

javascript
// 기본적인 값 변환
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((num) => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// 객체 배열 변환
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); // ['김철수', '이영희', '박민수']

// 인덱스 활용
const indexedNames = users.map((user, index) => `${index + 1}. ${user.name}`);
console.log(indexedNames); // ['1. 김철수', '2. 이영희', '3. 박민수']

실제 활용 사례

javascript
// API 응답 데이터 변환
const apiResponse = [
  { id: 1, first_name: 'John', last_name: 'Doe' },
  { id: 2, first_name: 'Jane', last_name: 'Smith' },
];

const formattedUsers = apiResponse.map((user) => ({
  id: user.id,
  fullName: `${user.first_name} ${user.last_name}`,
  displayName: `${user.last_name}, ${user.first_name}`,
}));

// React 컴포넌트 렌더링
const UserList = ({ users }) => (
  <ul>
    {users.map((user) => (
      <li key={user.id}>
        {user.name} ({user.age}세)
      </li>
    ))}
  </ul>
);
💡 기억할 점

map은 항상 새로운 배열을 반환하며, 원본 배열을 수정하지 않습니다. 각 요소에 대한 변환 로직을 적용할 때 사용하세요.

🔄 for 반복문의 현대적인 대안

map새로운 배열을 생성하는 것이 주 목적이지만, 콜백 함수를 호출할 수 있다는 점 때문에 전통적인 for 반복문을 대체하는 용도로도 많이 사용됩니다.

javascript
// ❌ 전통적인 for 반복문
const numbers = [1, 2, 3, 4, 5];
for (let i = 0; i < numbers.length; i++) {
  console.log(`인덱스 ${i}: ${numbers[i]}`);
}

// ✅ map을 활용한 함수형 접근
numbers.map((num, index) => {
  console.log(`인덱스 ${index}: ${num}`);
  return num; // 변환 로직이 필요 없어도 return 필요
});

// 더 깔끔한 방법: forEach 사용 (단순 반복만 필요한 경우)
numbers.forEach((num, index) => {
  console.log(`인덱스 ${index}: ${num}`);
});

언제 map을 사용할까?

  • 새로운 배열이 필요할 때: map 사용
  • 단순 반복만 필요할 때: forEach 사용 (가독성 향상)
  • 부수 효과만 필요한 경우: forEach가 더 적절
javascript
// 변환 + 반복의 결합
const processedData = rawData
  .map(item => transformItem(item))  // 변환
  .map((item, index) => {
    logProcessing(index, item);     // 부수 효과
    return item;
  });

2. filter - 조건에 맞는 요소만 골라내기

filter 메서드는 주어진 조건 함수를 통과하는 요소만을 모아 새로운 배열을 생성합니다. 원본 배열은 변경되지 않습니다.

기본 구문

javascript
const filteredArray = array.filter((element, index, array) => {
  // 조건 로직 (true를 반환하면 요소가 포함됨)
  return condition;
});

실전 예제

javascript
// 숫자 필터링
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]

const oddNumbers = numbers.filter((num) => num % 2 !== 0);
console.log(oddNumbers); // [1, 3, 5, 7, 9]

// 객체 배열 필터링
const products = [
  { id: 1, name: '노트북', price: 1000000, category: '전자제품' },
  { id: 2, name: '책상', price: 150000, category: '가구' },
  { id: 3, name: '키보드', price: 50000, category: '전자제품' },
  { id: 4, name: '의자', price: 200000, category: '가구' },
];

const electronics = products.filter(
  (product) => product.category === '전자제품'
);

const affordable = products.filter((product) => product.price <= 200000);

console.log(electronics); // 노트북, 키보드
console.log(affordable); // 책상, 키보드, 의자

실제 활용 사례

javascript
// 검색 기능 구현
const searchProducts = (products, query) => {
  return products.filter((product) =>
    product.name.toLowerCase().includes(query.toLowerCase())
  );
};

// 유효성 검증
const validUsers = users.filter(
  (user) => user.email && user.email.includes('@') && user.age >= 18
);

// 중복 제거 (map과 조합)
const uniqueCategories = [
  ...new Set(products.map((product) => product.category)),
];
⚠️ 주의사항

filter는 조건 함수가 true를 반환하는 요소만 포함합니다. false, null, undefined, 0, NaN, ''는 모두 false로 간주됩니다.

🔍 실무에서의 주요 사용 사례

filter조건에 맞는 요소만 추출하는 것이 주 목적이지만, 실제로는 다음과 같은 다양한 시나리오에서 핵심적인 역할을 합니다:

javascript
// 🔍 검색 및 필터링 UI
const handleSearch = (query) => {
  const filtered = products.filter(product =>
    product.name.toLowerCase().includes(query.toLowerCase()) ||
    product.category.toLowerCase().includes(query.toLowerCase())
  );
  setFilteredProducts(filtered);
};

// ✅ 데이터 유효성 검증
const validateUsers = (users) => {
  const valid = users.filter(user =>
    user.email?.includes('@') &&
    user.age >= 18 &&
    user.name?.trim().length > 0
  );
  const invalid = users.filter(user => !valid.includes(user));
  return { valid, invalid };
};

// 🗂️ 데이터 분류 및 정리
const categorizeProducts = (products) => ({
  expensive: products.filter(p => p.price > 100000),
  affordable: products.filter(p => p.price <= 100000),
  discontinued: products.filter(p => p.status === 'discontinued')
});

// 🔄 실시간 데이터 필터링 (React 등에서)
const visibleTodos = todos.filter(todo =>
  filter === 'all' ? true :
  filter === 'active' ? !todo.completed :
  filter === 'completed' ? todo.completed : true
);

3. reduce - 배열을 하나의 값으로 축소하기

reduce 메서드는 배열의 모든 요소를 하나의 값으로 축소합니다. 누적 계산, 그룹화, 변환 등 다양한 용도로 사용할 수 있습니다.

기본 구문

javascript
const result = array.reduce((accumulator, currentValue, index, array) => {
  // 축소 로직
  return newAccumulator;
}, initialValue);

실전 예제

javascript
// 합계 계산
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15

// 최대값 찾기
const max = numbers.reduce((acc, num) => Math.max(acc, num), -Infinity);
console.log(max); // 5

// 객체로 그룹화
const fruits = [
  { name: '사과', category: '과일' },
  { name: '바나나', category: '과일' },
  { name: '당근', category: '채소' },
  { name: '토마토', category: '채소' },
];

const groupedByCategory = fruits.reduce((acc, fruit) => {
  if (!acc[fruit.category]) {
    acc[fruit.category] = [];
  }
  acc[fruit.category].push(fruit.name);
  return acc;
}, {});

console.log(groupedByCategory);
// { 과일: ['사과', '바나나'], 채소: ['당근', '토마토'] }

실제 활용 사례

javascript
// 장바구니 총액 계산
const cart = [
  { name: '노트북', price: 1000000, quantity: 1 },
  { name: '마우스', price: 50000, quantity: 2 },
  { name: '키보드', price: 80000, quantity: 1 },
];

const totalPrice = cart.reduce(
  (total, item) => total + item.price * item.quantity,
  0
);

// 데이터 분석
const userStats = users.reduce(
  (stats, user) => {
    stats.totalAge += user.age;
    stats.count += 1;
    stats.averageAge = stats.totalAge / stats.count;
    return stats;
  },
  { totalAge: 0, count: 0, averageAge: 0 }
);

// 배열 평탄화
const nestedArrays = [
  [1, 2],
  [3, 4],
  [5, 6],
];
const flatArray = nestedArrays.reduce((acc, arr) => acc.concat(arr), []);
🚀 고급 활용

reducemap, filter의 기능을 모두 구현할 수 있을 만큼 강력합니다. 하지만 가독성을 위해 적절한 메서드를 선택하세요.

📊 실무에서의 다양한 활용 패턴

reduce배열을 하나의 값으로 축소하는 것이 주 목적이지만, 그 유연성 때문에 실제로는 다음과 같은 다양한 시나리오에서 핵심적인 역할을 합니다:

javascript
// 📈 통계 및 분석
const analyzeSales = (orders) => orders.reduce((stats, order) => {
  stats.totalRevenue += order.amount;
  stats.orderCount += 1;
  stats.averageOrder = stats.totalRevenue / stats.orderCount;
  return stats;
}, { totalRevenue: 0, orderCount: 0, averageOrder: 0 });

// 🏷️ 데이터 그룹화 및 분류
const groupByCategory = (products) => products.reduce((groups, product) => {
  const category = product.category;
  if (!groups[category]) groups[category] = [];
  groups[category].push(product);
  return groups;
}, {});

// 🔄 복잡한 데이터 변환
const transformData = (rawData) => rawData.reduce((result, item, index) => {
  result.processed.push({
    id: item.id,
    name: item.name.toUpperCase(),
    index: index + 1,
    isFirst: index === 0,
    isLast: index === rawData.length - 1
  });
  result.metadata.totalItems = index + 1;
  return result;
}, { processed: [], metadata: { totalItems: 0 } });

// 🧮 비동기 작업 체이닝
const processAsync = async (items) => {
  return items.reduce(async (promise, item) => {
    const result = await promise;
    const processedItem = await processItemAsync(item);
    result.push(processedItem);
    return result;
  }, Promise.resolve([]));
};

// 📝 객체 생성 및 변환
const arrayToObject = (array, keySelector) => array.reduce((obj, item) => {
  obj[keySelector(item)] = item;
  return obj;
}, {});

4. splice - 배열 직접 수정하기

splice 메서드는 배열의 요소를 추가, 삭제, 교체할 수 있습니다. 원본 배열을 직접 수정한다는 점이 다른 메서드들과 다릅니다.

기본 구문

javascript
// 요소 삭제
array.splice(startIndex, deleteCount);

// 요소 추가
array.splice(startIndex, 0, ...itemsToAdd);

// 요소 교체
array.splice(startIndex, deleteCount, ...itemsToAdd);

실전 예제

javascript
let fruits = ['사과', '바나나', '오렌지', '포도', '키위'];

// 요소 삭제
const removed = fruits.splice(2, 1); // 인덱스 2부터 1개 삭제
console.log(fruits); // ['사과', '바나나', '포도', '키위']
console.log(removed); // ['오렌지']

// 요소 추가
fruits.splice(2, 0, '체리', '블루베리'); // 인덱스 2에 추가
console.log(fruits); // ['사과', '바나나', '체리', '블루베리', '포도', '키위']

// 요소 교체
fruits.splice(1, 2, '망고', '파인애플'); // 인덱스 1부터 2개를 교체
console.log(fruits); // ['사과', '망고', '파인애플', '블루베리', '포도', '키위']

실제 활용 사례

javascript
// 할 일 목록 관리
class TodoList {
  constructor() {
    this.todos = [];
  }

  addTodo(todo, index = this.todos.length) {
    this.todos.splice(index, 0, todo);
  }

  removeTodo(index) {
    return this.todos.splice(index, 1)[0];
  }

  updateTodo(index, newTodo) {
    this.todos.splice(index, 1, newTodo);
  }
}

// 큐 구현
class Queue {
  constructor() {
    this.items = [];
  }

  enqueue(item) {
    this.items.splice(this.items.length, 0, item);
  }

  dequeue() {
    return this.items.splice(0, 1)[0];
  }
}
❌ 위험성

splice는 원본 배열을 직접 수정하므로, 불변성을 유지해야 하는 경우 주의해서 사용하세요. 가능하다면 slice, concat 등을 고려해보세요.

⚙️ 실무에서의 핵심 활용 사례

splice배열을 직접 수정하는 것이 주 목적이지만, 그 유연성 때문에 실제로는 다음과 같은 다양한 데이터 구조와 알고리즘 구현에 필수적입니다:

javascript
// 📋 할 일 관리 애플리케이션
class TodoManager {
  constructor() {
    this.todos = [];
  }

  addTodoAt(todo, index) {
    this.todos.splice(index, 0, { ...todo, id: Date.now() });
  }

  moveTodo(fromIndex, toIndex) {
    const [todo] = this.todos.splice(fromIndex, 1);
    this.todos.splice(toIndex, 0, todo);
  }

  replaceRange(startIndex, deleteCount, newTodos) {
    this.todos.splice(startIndex, deleteCount, ...newTodos);
  }
}

// 🎯 게임 개발에서의 배열 관리
class GameBoard {
  constructor(size) {
    this.board = Array(size * size).fill(null);
  }

  placePiece(position, piece) {
    this.board.splice(position, 1, piece);
  }

  removePiece(position) {
    this.board.splice(position, 1, null);
  }

  insertRow(rowIndex, newRow) {
    const startIndex = rowIndex * this.size;
    this.board.splice(startIndex, this.size, ...newRow);
  }
}

// 🔄 실시간 데이터 동기화
class LiveDataManager {
  constructor() {
    this.data = [];
  }

  // 서버에서 받은 변경사항 적용
  applyChanges(changes) {
    changes.forEach(change => {
      switch(change.type) {
        case 'insert':
          this.data.splice(change.index, 0, change.item);
          break;
        case 'update':
          this.data.splice(change.index, 1, change.item);
          break;
        case 'delete':
          this.data.splice(change.index, 1);
          break;
      }
    });
  }
}

// 📊 정렬 알고리즘 구현
function insertionSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    const current = arr[i];
    let j = i - 1;

    while (j >= 0 && arr[j] > current) {
      arr.splice(j + 1, 1, arr[j]); // 요소 이동
      j--;
    }
    arr.splice(j + 1, 1, current);
  }
  return arr;
}

메서드 비교 및 선택 가이드

메서드반환값원본 수정용도
map새로운 배열요소 변환
filter새로운 배열요소 필터링
reduce단일 값축소/집계
splice삭제된 요소 배열직접 수정

실전 팁과 모범 사례

1

불변성 유지하기

가능하다면 splice 대신 map, filter, reduce를 사용하세요. React와 같은 라이브러리에서는 불변성이 중요합니다.

javascript
// ❌ 좋지 않은 예
const addItem = (array, item) => {
  array.push(item); // 원본 수정
  return array;
};

// ✅ 좋은 예
const addItem = (array, item) => [...array, item]; // 불변성 유지
2

타입 안전성 확보

TypeScript를 사용할 때는 제네릭을 활용하여 타입 안정성을 높이세요.

typescript
interface User {
  id: number;
  name: string;
  age: number;
}

const users: User[] = [/* ... */];

// 타입 추론이 제대로 작동함
const names = users.map(user => user.name); // string[]
const adults = users.filter(user => user.age >= 18); // User[]
3

에러 처리

배열 메서드 사용 시 예외 상황을 고려하세요.

javascript
// 안전한 reduce 사용
const safeSum = (numbers: number[]) => {
  return numbers?.reduce((sum, num) => sum + num, 0) ?? 0;
};

// 빈 배열 처리
const getFirstEven = (numbers: number[]) => {
  return numbers.find(num => num % 2 === 0) ?? null;
};
4

메서드 체이닝

적절한 체이닝으로 코드 가독성을 높이세요.

javascript
const result = users
  .filter(user => user.isActive)
  .map(user => ({
    id: user.id,
    displayName: `${user.firstName} ${user.lastName}`
  }))
  .sort((a, b) => a.displayName.localeCompare(b.displayName));

결론

JavaScript의 map, filter, reduce, splice는 배열을 효과적으로 다루기 위한 핵심 메서드들입니다. 각 메서드의 특징을 이해하고 적절한 상황에 맞게 사용한다면 더 깔끔하고 효율적인 코드를 작성할 수 있습니다.

다음 단계

이 메서드들을 익혔다면 이제 find, some, every, flatMap 등의 다른 배열 메서드들도 공부해보세요. 또한 함수형 프로그래밍 개념과 결합하면 더욱 강력한 코드를 작성할 수 있습니다.