7편. 컬렉션 이해하기: 배열, Map, Set, groupBy, WeakMap
📚 목차
1. 배열 핵심 패턴: map / filter / reduce / flatMap / some / every
2. 배열 검색 & 변환 고급 기능: find / findIndex / findLast / findLastIndex
3. Map & Set 기본 컬렉션
4. Map & Set 확장 기능 (ES2024)
5. WeakMap / WeakSet (메모리 관리 목적)
✔ 마무리

📂 [GitHub 실습 코드 보러가기] (https://github.com/cericube/nodejs-tutorials) /JavaScript
1. 배열 핵심 패턴: map / filter / reduce / flatMap / some / every
JavaScript에서 배열을 다루는 가장 강력한 도구는 바로 고차 함수(Higher-Order Functions)입니다.
이 함수들은 원본 배열을 직접 수정하지 않고, 항상 새로운 값을 만들어 내기 때문에 코드가 더 안전하고 예측 가능해집니다.
또한, for문보다 훨씬 선언적(Declarative)이어서 의도가 명확하게 드러나기 때문에 실무 코드에서도 반드시 익혀야 하는 핵심 패턴들입니다.
아래에서는 각 함수가 “언제 / 왜 / 어떻게” 쓰이는지, 직관적으로 이해할 수 있도록 설명과 예제를 함께 정리했습니다.
🔷 1) map - 배열을 '다른 형태'로 변환할 때
map은 배열의 각 요소를 하나씩 변환해 동일한 길이의 새로운 배열을 만듭니다.
데이터 형식 변경, 새로운 속성 추가, 원하는 필드 추출 등에 널리 활용됩니다.
const products = [
{ id: 101, name: 'Laptop', price: 1200 },
{ id: 102, name: 'Mouse', price: 25 },
];
// 예시 1: 각 상품의 세금 포함 가격(finalPrice)을 계산한 새로운 배열 생성
const productsWithTax = products.map(product => ({
...product, // 기존 속성 복사 (id, name, price 그대로 유지)
finalPrice: Math.round(product.price * 1.1), // 10% 세금 포함한 가격 추가
}));
// 결과:
// [
// { id: 101, name: 'Laptop', price: 1200, finalPrice: 1320 },
// { id: 102, name: 'Mouse', price: 25, finalPrice: 28 }
// ]
🔷 2) filter - 배열에서 '필요한 것만' 골라낼 때
filter는 조건을 만족하는 요소만 남기고, 나머지는 제거한 새 배열을 반환합니다.
특정 상태만 남겨야 할 때 또는 조건에 맞지 않는 데이터를 제외할 때 유용합니다.
const userList = [
{ name: 'Alice', status: 'active', role: 'admin' },
{ name: 'Bob', status: 'inactive', role: 'user' },
{ name: 'Charlie', status: 'active', role: 'user' },
];
// 예시 2: 활성(active) 상태 & 역할(role)이 user인 사용자만 추출
const activeUsers = userList.filter(user =>
user.status === 'active' && user.role === 'user'
);
// 결과:
// [ { name: 'Charlie', status: 'active', role: 'user' } ]
🔷 3) reduce - 여러 값을 하나의 결과로 '누적' 또는 '요약'할 때
reduce는 배열 전체를 순회하며 값을 누적(accumulate)하여 단 하나의 결과를 만들어 냅니다.
총계 계산, 통계 요약, 배열 → 객체 변환 등에서 실무 활용도가 매우 높습니다.
✔️ reduce 예제 A: 숫자 누적
const items = [
{ item: 'A', count: 3 },
{ item: 'B', count: 5 },
{ item: 'C', count: 1 },
];
// 예시 3A: 모든 count 값을 더하여 총합 구하기
const totalCount = items.reduce((accumulator, current) => {
return accumulator + current.count; // 현재 항목의 count를 누적
}, 0); // 초기값(initialValue): 0부터 누적 시작
// 결과: 9
✔️ reduce 예제 B: 배열을 '객체 형태'로 변환하기
// 예시 3B: item 값을 key로 하고 count를 value로 하는 객체로 변환
const itemsMap = items.reduce((acc, current) => {
acc[current.item] = current.count; // 객체에 동적으로 key 추가
return acc;
}, {}); // 초기값: 빈 객체로 시작
// 결과:
// { A: 3, B: 5, C: 1 }
🔷 4) flatMap - 배열을 변환한 후 '한 단계 평탄화'까지 한번에
flatMap은 이름 그대로 map + flat(1 depth)이 결합된 함수입니다.
“한 단계 평탄화(flat)”는 배열의 깊이를 1만 줄이는 것일 뿐, 결과가 반드시 1차원 배열이 되는 것은 아니다.
########################
# 1단계 평탄화 예시
########################
# 중첨 배열
[ A, [ B, C ], [ D, [ E ] ] ]
# 1단계 평탄화(flat 1)
[ A, B, C, D, [ E ] ]
예를 들어, 각 요소가 배열을 포함할 때 이를 바로 하나의 배열로 합쳐야 하는 경우 유용합니다.
const departments = [
{ name: 'Sales', employees: ['Sam', 'Tom'] },
{ name: 'Tech', employees: ['Mike', 'Lisa', 'Jenny'] },
];
// 예시 4: 모든 부서의 직원 배열을 하나로 합치기
const allEmployees = departments.flatMap(dept => dept.employees);
// 결과:
// ['Sam', 'Tom', 'Mike', 'Lisa', 'Jenny']
🔷 5) some / every - 배열 요소들이 '조건을 만족하는지 검사'할 때
조건 검사는 실무에서 특히 자주 등장하며, some과 every는 이를 효율적으로 해결합니다.
▸ some: 하나라도 조건을 만족하면 true
▸ every: 모두 조건을 만족해야 true
→ 둘 다 조건이 판명되면 즉시 순회를 멈추기 때문에 효율적입니다.
const formFields = [
{ name: 'title', isValid: true },
{ name: 'content', isValid: false },
{ name: 'tags', isValid: true },
];
// 예시 5A: 유효하지 않은 필드가 하나라도 있는가?
const hasInvalidField = formFields.some(field => field.isValid === false);
// 결과: true (content가 false이므로 즉시 true 반환)
// 예시 5B: 모든 필드가 유효한가?
const allFieldsValid = formFields.every(field => field.isValid === true);
// 결과: false
2. 배열 검색 & 변환 고급 기능: find / findIndex / findLast / findLastIndex
배열에서 특정 조건을 만족하는 요소(값) 또는 인덱스(위치)를 찾아야 할 때 사용하는 메서드들입니다.
이 함수들의 큰 장점은
▸ 조건을 만족하는 순간 즉시 탐색을 중단한다는 점
▸ map/filter처럼 전체를 끝까지 순회할 필요가 없습니다
▸ 직관적이며 실무 코드에서 조건 검색을 매우 깔끔하게 표현할 수 있습니다
| 메서드 | 반환값 | 방향 | 예시 |
| find | 요소(값) | 앞 → 뒤 | 첫 매칭 요소 찾기 |
| findLast | 요소(값) | 뒤 → 앞 | 최신/가장 마지막 매칭 요소 |
| findIndex | 인덱스(숫자) | 앞 → 뒤 | 특정 조건의 위치 찾기 |
| findLastIndex | 인덱스(숫자) | 뒤 → 앞 | 마지막으로 나타난 위치 찾기 |
ES2023에서는 기존 find, findIndex에 더해 역방향으로 검색하는 findLast, findLastIndex가 추가되어 더욱 편리해졌습니다.
🔷 1) 요소 검색: find / findLast (ES2023)
▸ find: 배열의 앞쪽(0번 인덱스)부터 조건에 맞는 첫 요소를 반환
▸ findLast: 뒤쪽(마지막 인덱스)부터 조건에 맞는 첫 요소를 반환
특히 findLast는 “가장 최신 기록 찾기”, “가장 가까운 조건 충족 값 찾기” 같은 상황에서 매우 유용합니다.
✔️ 예제: 리비전(revision) 중 특정 작성자(author)의 수정 기록 찾기
▸ findLast 덕분에 최신 항목을 찾기 위해 배열을 뒤집거나 정렬할 필요가 없습니다.
▸ 실무에서 로그나 리비전 기록 검색 시 매우 큰 이점입니다.
const revisions = [
{ version: 1, author: 'A', date: '2024-01-01' },
{ version: 2, author: 'B', date: '2024-03-15' },
{ version: 3, author: 'A', date: '2024-05-20' }, // 가장 최근 A의 작업
];
// 예시 1A (find):
// 배열 앞쪽부터 순회하여 author가 'A'인 첫 요소를 반환합니다.
const firstRevisionByA = revisions.find(rev => rev.author === 'A');
// 결과: { version: 1, author: 'A', date: '2024-01-01' }
// 예시 1B (findLast):
// 배열 뒤쪽부터 순회하여 author가 'A'인 요소를 찾습니다.
// => 최신 수정 기록에 쉽게 접근 가능
const lastRevisionByA = revisions.findLast(rev => rev.author === 'A');
// 결과: { version: 3, author: 'A', date: '2024-05-20' }
🔷 2) 인덱스 검색: findIndex / findLastIndex (ES2023)
이번에는 요소 자체가 아니라, 해당 요소가 “몇 번째 위치”에 있는지를 알고 싶을 때 사용하는 메서드입니다.
▸ findIndex: 앞에서부터 조건을 만족하는 첫 번째 인덱스
▸ findLastIndex: 뒤에서부터 조건을 만족하는 첫 번째(= 마지막) 인덱스
특정 상태의 위치를 찾거나, 일부만 업데이트해야 할 때 유용합니다.
✔️ 예제: 상태 문자열 배열에서 특정 상태의 위치 찾기
▸ findLastIndex를 사용하면 뒤쪽에서 먼저 찾고 싶은 조건을 자연스럽게 표현할 수 있습니다.
▸ 예: "마지막 실패 지점", "최근 상태 변화 위치", "마지막으로 pending 상태가 된 시점" 등.
const statusList = ['pending', 'success', 'error', 'pending', 'complete'];
// 예시 2A (findIndex):
// 배열의 앞쪽부터 순회하여 'error' 상태가 처음 등장하는 위치를 반환합니다.
const firstErrorIndex = statusList.findIndex(status => status === 'error');
// 결과: 2 // 0: pending, 1: success, 2: error
// 예시 2B (findLastIndex):
// 배열의 뒤쪽부터 순회하여 'pending'이 마지막으로 등장한 위치를 반환합니다.
const lastPendingIndex = statusList.findLastIndex(status => status === 'pending');
// 결과: 3 // 뒤에서 찾으면 인덱스 3이 가장 먼저 발견됨
3. Map & Set 기본 컬렉션
JavaScript에서 데이터를 관리할 때는 보통 Object와 Array를 많이 사용하지만, 프로젝트 규모가 커질수록 이 구조만으로는 다음과 같은 한계가 발생합니다.
▸ 모든 객체(Object)의 key는 반드시 문자열 또는 Symbol이어야 함
▸ 배열(Array)로 membership 체크(.includes) 시 성능 저하
▸ 객체 키의 삽입 순서 보장 X
▸ 대량 데이터 관리 시 불편함 증가
이러한 문제를 해결하기 위해 도입된 구조가 Map과 Set입니다.
두 컬렉션은 더 유연하고, 더 직관적이며, 성능까지 우수해 실무에서 기본 도구로 자리 잡았습니다.
🔷 1) Map - 더 유연하고 강력한 '키-값 저장소'
Map은 기본 Object와 비슷하게 키(key)와 값(value)을 저장하지만, 다음과 같은 중요한 차이가 있습니다.
✔️ Map이 Object보다 우수한 점
▸ 모든 타입(객체, 배열, 함수 등)을 키로 사용할 수 있음
▸ key 삽입 순서가 보존됨
▸ .size 속성으로 Map의 크기를 **O(1)**로 바로 조회 가능
▸ key 충돌 위험이 없어 안정적
▸ 캐시(Cache), 설정 테이블, 객체간 매핑 등에 매우 적합
1. 예시: Set의 다양한 메서드 테스트 코드
// -------------------------------------------
// 1. Map 생성 & 기본 set/get
// -------------------------------------------
// 키로 사용할 다양한 타입
const objKey = { type: 'objectKey' };
const arrKey = ['arrayKey'];
const fnKey = function fnKey() {};
const map = new Map();
// set(): 키-값 저장
map.set(objKey, { message: 'Object key 사용 예시' });
map.set(arrKey, { message: 'Array key 사용 예시' });
map.set(fnKey, { message: 'Function key 사용 예시' });
// get(): 키로 값 조회
console.log('--- get() 테스트 ---');
console.log(map.get(objKey)); // { message: 'Object key 사용 예시' }
console.log(map.get(arrKey)); // { message: 'Array key 사용 예시' }
console.log(map.get(fnKey)); // { message: 'Function key 사용 예시' }
// -------------------------------------------
// 2. has(): 키 존재 여부 빠르게 확인
// -------------------------------------------
console.log('\n--- has() 테스트 ---');
console.log(map.has(arrKey)); // true
console.log(map.has('notExist')); // false
// -------------------------------------------
// 3. size: Map 요소 개수 → O(1)로 성능 매우 좋음
// -------------------------------------------
console.log('\n--- size 테스트 ---');
console.log(`Map 크기: ${map.size}개`);
// -------------------------------------------
// 4. delete(): 특정 키 삭제
// -------------------------------------------
console.log('\n--- delete() 테스트 ---');
map.delete(fnKey);
console.log(map.has(fnKey)); // false → 삭제 확인
console.log(`삭제 후 Map 크기: ${map.size}개`);
// -------------------------------------------
// 5. keys(), values(), entries(): 순회 관련 메서드
// -------------------------------------------
console.log('\n--- keys(), values(), entries() 순회 ---');
console.log('keys():');
for (const key of map.keys()) {
console.log(' key =', key);
}
console.log('values():');
for (const value of map.values()) {
console.log(' value =', value);
}
console.log('entries():');
for (const [key, value] of map.entries()) {
console.log(` key = ${JSON.stringify(key)}, value = ${JSON.stringify(value)}`);
}
// -------------------------------------------
// 6. forEach(): Map에서도 forEach 사용 가능
// -------------------------------------------
console.log('\n--- forEach() 순회 ---');
map.forEach((value, key) => {
console.log(`key: ${JSON.stringify(key)}, value: ${JSON.stringify(value)}`);
});
// -------------------------------------------
// 7. clear(): 전체 요소 삭제
// -------------------------------------------
// map.clear();
// console.log(`clear() 후 size = ${map.size}`);
2. 예시: Map을 활용한 캐시(Cache) 패턴
const cache = new Map();
function heavyCompute(num) {
console.log('💡 무거운 작업 실행');
return num * num;
}
function getCachedValue(num) {
if (cache.has(num)) {
return cache.get(num); // 캐시된 값 반환
}
const result = heavyCompute(num);
cache.set(num, result);
return result;
}
console.log(getCachedValue(10)); // 무거운 작업 실행 → 100
console.log(getCachedValue(10)); // 캐시 HIT → 빠르게 100
3. 예시: 배열을 Map으로 변환하여 빠르게 조회
console.log('\n--- 배열 데이터 → Map 변환 ---');
//데이터가 많아질수록 배열로 조회하는 것보다 Map으로 조회하는 것이 훨씬 빠릅니다.
const products = [
{ id: 101, name: 'Laptop', price: 1200 },
{ id: 102, name: 'Mouse', price: 25 }
];
const productMap = new Map(
products.map(item => [item.id, item])
);
console.log(productMap.get(101)); // Laptop
console.log(productMap.get(102)); // Mouse
🔷 Set - 유일한 값만 저장하는 집합 구조
Set은 “중복을 허용하지 않는 집합” 구조입니다.
배열과 비슷해 보이지만, 아래 장점이 실무에서 매우 유용합니다.
✔️ Set의 장점
▸ 중복 값 자동 제거
▸ .has()가 O(1) → 대량 데이터에서도 빠른 membership 체크 가능(.includes() 보다 빠름)
▸ 배열보다 의도가 더 명확함 (“유일한 값의 집합”)
1. 예시: 중복 제거
console.log('\n--- 실무 예시 1: 배열 중복 제거 ---');
const tags = ['js', 'html', 'css', 'js', 'react', 'css'];
const uniqueTags = [...new Set(tags)];
console.log('중복 제거 결과:', uniqueTags);
// ['js', 'html', 'css', 'react']
2. 예시: Membership Check (.includes() 보다 빠름)
Set의 .has()는 속도가 매우 빠르므로 “이 값이 목록에 포함되어 있나?” 같은 체크에 최적입니다.
console.log('\n--- 실무 예시 2: 빠른 포함 여부 체크 ---');
const restrictedIPs = new Set([
'192.168.1.1',
'10.0.0.5',
'172.16.0.10'
]);
const tryIP = '10.0.0.5';
if (restrictedIPs.has(tryIP)) {
console.log(`${tryIP} → 접근 제한 IP`);
} else {
console.log(`${tryIP} → 허용됨`);
}
3. Set로 리스트 필터링 최적화
console.log('\n--- 실무 예시 5: Set로 빠른 필터링 ---');
const allowedRoles = new Set(['admin', 'manager']);
const users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'guest' },
{ name: 'Charlie', role: 'manager' },
];
const allowedUsers = users.filter(u => allowedRoles.has(u.role));
console.log('허용된 사용자:', allowedUsers);
// Alice, Charlie
4. 예시: Set의 다양한 메서드 테스트 코드
// -------------------------------------------
// 1. Set 생성 & add()
// -------------------------------------------
const set = new Set();
// add(): 값 추가 (중복은 자동 무시됨)
set.add(1);
set.add(2);
set.add(2); // 중복 → 무시됨
set.add(3);
console.log('현재 Set 내용:', [...set]); // [1, 2, 3]
// -------------------------------------------
// 2. has(): 포함 여부 확인 (O(1))
// -------------------------------------------
console.log('\n--- has() 테스트 ---');
console.log(set.has(2)); // true
console.log(set.has(5)); // false
// -------------------------------------------
// 3. size: Set 길이 확인 (O(1))
// -------------------------------------------
console.log('\n--- size 테스트 ---');
console.log(`Set 크기: ${set.size}`); // 3
// -------------------------------------------
// 4. delete(): 특정 값 삭제
// -------------------------------------------
console.log('\n--- delete() 테스트 ---');
set.delete(2);
console.log(set.has(2)); // false
console.log('삭제 후 Set:', [...set]); // [1, 3]
// -------------------------------------------
// 5. for...of, forEach() 사용한 순회
// -------------------------------------------
console.log('\n--- 순회 테스트 ---');
console.log('for...of:');
for (const value of set) {
console.log(' value =', value);
}
console.log('forEach():');
set.forEach(value => {
console.log(' value =', value);
});
// -------------------------------------------
// 6. clear(): 전체 삭제
// -------------------------------------------
// set.clear();
// console.log(set.size); // 0
4. Map & Set 확장 기능 (ES2024)
ES2024에서는 Map과 Set을 더 강력하게 만들어주는 기능들이 추가되었습니다.
특히 Object.groupBy / Map.groupBy는 기존에 reduce를 사용해 어렵게 작성하던 "그룹화(grouping)" 작업을 매우 간결하게 만들고, Set의 집합 연산(union, intersection, difference)은 자료 분석·권한 처리·필터링 등 다양한 실무 상황에서 유용합니다.
🔷 1) Object.groupBy / Map.groupBy (ES2024)
배열의 요소를 어떤 기준(키)으로 그룹화하여 묶어주는 기능입니다.
✔️ Object.groupBy - 그룹 결과가 “일반 객체(Object)”로 반환됨
▸ 키는 문자열(string)이어야 함
▸ JSON 직렬화가 쉬움
▸ 간단한 데이터 그룹화에 적합
//예제: status(상태)별 그룹화
const orders = [
{ id: 1, status: 'pending', region: 'Seoul' },
{ id: 2, status: 'shipped', region: 'Busan' },
{ id: 3, status: 'pending', region: 'Seoul' },
];
// Object.groupBy → 결과는 일반 객체
//order => order.status
//function (order) {
// return order.status;
//}
//원하는 모든 조건 / 로직 / 계산식을 넣을 수 있습니다.
const ordersByStatus = Object.groupBy(orders, order => order.status);
/*
결과:
{
pending: [
{ id: 1, status: 'pending', region: 'Seoul' },
{ id: 3, status: 'pending', region: 'Seoul' }
],
shipped: [
{ id: 2, status: 'shipped', region: 'Busan' }
]
}
*/
console.log(ordersByStatus.pending.length); // 2
reduce 사용하는 방식 비교
# reduce 사용하는 방식
const grouped = orders.reduce((acc, order) => {
const key = order.status;
if (!acc[key]) acc[key] = [];
acc[key].push(order);
return acc;
}, {});
그룹화 방법 예시
// 1) 가격 기준으로 그룹화 (비싼 제품 / 저렴한 제품)
Object.groupBy(orders, order =>
order.price > 100 ? 'expensive' : 'cheap'
);
// 2) 날짜 기준으로 그룹화 (YYYY-MM)
Object.groupBy(orders, order =>
order.date.slice(0, 7) // "2024-05"
);
// 3) 사용자 역할(role) + 상태(status) 결합
Object.groupBy(users, user => `${user.role}_${user.status}`);
✔️ Map.groupBy - 그룹 결과가 “Map”으로 반환됨
▸ 키로 객체, 숫자 등 모든 타입 가능
▸ 자료형 제약 없음 → 더 유연
▸ 대규모 데이터 처리에 적합
//예제: 지역(region)별 Map 그룹화
// Map.groupBy → 결과는 Map
const ordersByRegionMap = Map.groupBy(orders, order => order.region);
/*
결과:
Map(2) {
'Seoul' => [
{ id: 1, status: 'pending', region: 'Seoul' },
{ id: 3, status: 'pending', region: 'Seoul' }
],
'Busan' => [
{ id: 2, status: 'shipped', region: 'Busan' }
]
}
*/
console.log(ordersByRegionMap.get('Seoul').length); // 2
console.log(ordersByRegionMap.has('Busan')); // true
🔷 2) Set 연산 메서드 (ES2024)
Set은 원래 “고유한 값들의 집합”을 저장하는 자료구조입니다.
ES2024부터는 수학적 집합 연산을 바로 사용할 수 있게 되어 활용도가 크게 올라갔습니다.
| 메서드 | 의미 | 설명 |
| union(other) | 합집합 A ∪ B | 두 Set의 모든 요소를 합쳐 새로운 Set 반환 |
| intersection(other) | 교집합 A ∩ B | 두 Set에 모두 존재하는 값만 반환 |
| difference(other) | 차집합 A \ B | A에는 있고 B에는 없는 값만 반환 |
const userPermissions = new Set(['read', 'write', 'admin']);
const defaultPermissions = new Set(['read', 'guest']);
// intersection: 공통 권한 (A ∩ B)
const commonRights = userPermissions.intersection(defaultPermissions);
/*
Set { 'read' }
*/
console.log([...commonRights]); // ['read']
// difference: 사용자에게만 있는 권한 (A \ B)
const exclusiveRights = userPermissions.difference(defaultPermissions);
/*
Set { 'write', 'admin' }
*/
console.log([...exclusiveRights]); // ['write', 'admin']
// union: 전체 권한 목록 (A ∪ B)
const allRights = userPermissions.union(defaultPermissions);
/*
Set { 'read', 'write', 'admin', 'guest' }
*/
console.log([...allRights]); // ['read', 'write', 'admin', 'guest']
5. WeakMap / WeakSet (메모리 관리 목적)
WeakMap과 WeakSet은 일반적인 Map 및 Set과 유사하지만, 저장된 객체에 대한 참조를 약하게(Weak) 유지하는 특수 목적의 컬렉션입니다.
WeakMap과 WeakSet은 저장된 객체에 대한 참조를 약한 참조(Weak Reference)로 유지합니다.
즉, 외부 코드에서 그 객체를 더 이상 참조하지 않게 되면, 가비지 컬렉터(GC)가 그 객체를 자동으로 제거하며, Weak 컬렉션 내부의 데이터도 함께 사라집니다.
| 구분 | 특징 | 사용 예시 |
| WeakMap | 키가 반드시 객체. 키에 대한 외부 참조가 끊기면 WeakMap 항목도 자동 삭제 | DOM 요소·객체 인스턴스에 설정/캐시/메타데이터 연결할 때 |
| WeakSet | 값이 반드시 객체. 외부 참조가 없으면 WeakSet 항목도 자동 삭제 | 객체가 처리되었는지 여부를 기록하는 “마커(visited)” 역할 |
✔️ 왜 이것이 중요할까?
일반 Map/Set에 객체를 넣어두면 그 객체가 사용되지 않아도 Map/Set이 계속 참조를 유지하기 때문에 가비지 컬렉터가 메모리를 회수할 수 없습니다.
→ 이 상태가 바로 메모리 누수(Memory Leak)
반면 WeakMap·WeakSet은 "외부에서 해당 객체를 안 쓰면 컬렉션에서도 알아서 사라지는" 구조여서 메모리 누수 위험을 줄여줍니다.
✔️ WeakMap 동작 예시
let element = document.getElementById('box');
const wm = new WeakMap();
wm.set(element, { clicked: 0 });
1. 이 상태에서
WeakMap ----> element
element ----> 실제 DOM 요소
DOM 요소가 외부에서 사용되고 있기 때문에 GC 대상이 아닙니다.
2. 그런데 DOM 요소가 제거되면?
element = null; // 외부 참조 제거!
3. 이 순간:
▸ element(변수)는 null이 되었고
▸ DOM 요소는 더 이상 어떤 변수에도 참조되지 않음
▸ WeakMap은 “약한 참조”만 있으므로 GC가 제거할 수 있음
▸ DOM 요소가 GC로 사라질 때, WeakMap 내부 데이터도 함께 자동 삭제됨
즉, WeakMap 때문에 메모리 누수가 일어나지 않음.
✔️ 일반 실무에서 WeakMap / WeakSet 사용 빈도가 낮은 이유
1. 메모리 관리용 특수 도구라서
▸ WeakMap과 WeakSet은 주로 프레임워크 내부나 엔진 레벨에서 메모리 누수를 방지하려고 사용됩니다.
▸ 일반 애플리케이션 개발자는 이미 이런 환경 위에서 작업하기 때문에 직접 사용할 일이 거의 없습니다.
2. 순회(iteration)와 크기(size) 확인이 불가능해서
▸ WeakMap / WeakSet은 for-of, keys(), size 등 기본 컬렉션 기능을 제공하지 않습니다.
▸ 데이터 목록을 조회하거나 개수를 알고 싶은 일반적인 비즈니스 로직에는 적합하지 않습니다.
3. 객체만 저장할 수 있는 제한 때문에
▸ 키(WeakMap) 또는 값(WeakSet)이 반드시 객체여야 합니다.
▸ 문자열·숫자 같은 기본 데이터 타입을 사용할 수 없어 실제 활용 범위가 좁습니다.
✔ 마무리
이번 장에서는 JavaScript의 핵심 데이터 구조인 배열, Map, Set, 그리고 고급 기능인 groupBy(ES2024), Set 연산, WeakMap/WeakSet까지 함께 살펴보았습니다.
각 컬렉션은 사용 목적과 장점이 분명히 다르며, 적절한 상황에서 올바른 자료구조를 선택하는 것이 코드 품질과 성능을 크게 좌우합니다.
▸ 배열은 선언적 고차 함수로 데이터 흐름을 깔끔하게 만들고
▸ Map과 Set은 더 빠른 조회와 명확한 의도를 제공하며
▸ groupBy와 Set 연산은 복잡한 데이터 가공을 단순화하고
▸ Weak 컬렉션은 프레임워크 수준에서 메모리 관리를 지원합니다.
이 장의 내용을 이해하면 데이터 구조 선택 → 처리 로직 구현 → 성능·가독성 향상으로 이어지는 실무 개발의 중요한 기반을 단단하게 다질 수 있습니다.
※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.
'3.SW개발 > Node.js' 카테고리의 다른 글
| [JavaScript] 9편. 에러 처리 기법 이해하기 (0) | 2025.12.09 |
|---|---|
| [JavaScript] 8편. 반복문 이해하기: for, while, forEach, Iterable (0) | 2025.12.08 |
| [JavaScript] 6편. 모듈 시스템 이해하기: ESM, import/export (1) | 2025.12.07 |
| [JavaScript] 5편. 비동기 처리와 이벤트 루프 이해하기 : Promise, async/await, Event Loop (0) | 2025.12.06 |
| [JavaScript] 4편. 클래스(Class) & 프로토타입(Prototype) 이해하기 (1) | 2025.12.06 |