JavaScript 배열 필수 메서드 완벽 가이드
map, filter, reduce, splice의 개념부터 실전 활용까지
JavaScript 배열 필수 메서드 완벽 가이드
현대 JavaScript 개발에서 배열은 가장 많이 사용되는 데이터 구조 중 하나입니다. 특히 map, filter, reduce, splice와 같은 메서드들은 배열을 효과적으로 다루기 위한 필수 도구입니다. 이 글에서는 각 메서드의 기본 개념부터 실전 활용까지 자세히 알아보겠습니다.
학습 목표
이 글을 통해 다음과 같은 내용을 배울 수 있습니다:
- 각 메서드의 기본 개념과 사용법
- 불변성(Immutability)의 개념과 중요성
- 실제 프로젝트에서의 활용 사례
- 메서드 간의 차이점과 선택 기준
1. map - 변환의 마법사
map 메서드는 배열의 각 요소를 변환하여 새로운 배열을 생성합니다. 원본 배열은 변경되지 않으며, 항상 같은 길이의 새로운 배열을 반환합니다.
기본 구문
const newArray = array.map((element, index, array) => {
// 변환 로직
return transformedElement;
});
실전 예제
// 기본적인 값 변환
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. 박민수']
실제 활용 사례
// 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 반복문을 대체하는 용도로도 많이 사용됩니다.
// ❌ 전통적인 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가 더 적절
// 변환 + 반복의 결합
const processedData = rawData
.map(item => transformItem(item)) // 변환
.map((item, index) => {
logProcessing(index, item); // 부수 효과
return item;
});
2. filter - 조건에 맞는 요소만 골라내기
filter 메서드는 주어진 조건 함수를 통과하는 요소만을 모아 새로운 배열을 생성합니다. 원본 배열은 변경되지 않습니다.
기본 구문
const filteredArray = array.filter((element, index, array) => {
// 조건 로직 (true를 반환하면 요소가 포함됨)
return condition;
});
실전 예제
// 숫자 필터링
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); // 책상, 키보드, 의자
실제 활용 사례
// 검색 기능 구현
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는 조건에 맞는 요소만 추출하는 것이 주 목적이지만, 실제로는 다음과 같은 다양한 시나리오에서 핵심적인 역할을 합니다:
// 🔍 검색 및 필터링 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 메서드는 배열의 모든 요소를 하나의 값으로 축소합니다. 누적 계산, 그룹화, 변환 등 다양한 용도로 사용할 수 있습니다.
기본 구문
const result = array.reduce((accumulator, currentValue, index, array) => {
// 축소 로직
return newAccumulator;
}, initialValue);
실전 예제
// 합계 계산
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);
// { 과일: ['사과', '바나나'], 채소: ['당근', '토마토'] }
실제 활용 사례
// 장바구니 총액 계산
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), []);
🚀 고급 활용
reduce는 map, filter의 기능을 모두 구현할 수 있을 만큼 강력합니다.
하지만 가독성을 위해 적절한 메서드를 선택하세요.
📊 실무에서의 다양한 활용 패턴
reduce는 배열을 하나의 값으로 축소하는 것이 주 목적이지만, 그 유연성 때문에 실제로는 다음과 같은 다양한 시나리오에서 핵심적인 역할을 합니다:
// 📈 통계 및 분석
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 메서드는 배열의 요소를 추가, 삭제, 교체할 수 있습니다. 원본 배열을 직접 수정한다는 점이 다른 메서드들과 다릅니다.
기본 구문
// 요소 삭제
array.splice(startIndex, deleteCount);
// 요소 추가
array.splice(startIndex, 0, ...itemsToAdd);
// 요소 교체
array.splice(startIndex, deleteCount, ...itemsToAdd);
실전 예제
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); // ['사과', '망고', '파인애플', '블루베리', '포도', '키위']
실제 활용 사례
// 할 일 목록 관리
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는 배열을 직접 수정하는 것이 주 목적이지만, 그 유연성 때문에 실제로는 다음과 같은 다양한 데이터 구조와 알고리즘 구현에 필수적입니다:
// 📋 할 일 관리 애플리케이션
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 | 삭제된 요소 배열 | ✅ | 직접 수정 |
실전 팁과 모범 사례
불변성 유지하기
가능하다면 splice 대신 map, filter, reduce를 사용하세요. React와 같은 라이브러리에서는 불변성이 중요합니다.
// ❌ 좋지 않은 예
const addItem = (array, item) => {
array.push(item); // 원본 수정
return array;
};
// ✅ 좋은 예
const addItem = (array, item) => [...array, item]; // 불변성 유지
타입 안전성 확보
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[]
에러 처리
배열 메서드 사용 시 예외 상황을 고려하세요.
// 안전한 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;
};
메서드 체이닝
적절한 체이닝으로 코드 가독성을 높이세요.
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 등의 다른 배열
메서드들도 공부해보세요. 또한 함수형 프로그래밍 개념과 결합하면 더욱 강력한
코드를 작성할 수 있습니다.