JavaScript Set과 Map 사용하기
예제 기준 · JavaScript : 표준 내장 Set·Map·Array.from() (별도 라이브러리 없음)
Set은 중복 없는 값의 집합이고, Map은 key와 value를 연결하는 자료구조입니다. 배열로 순서가 있는 목록을 다루거나 일반 객체로 정해진 필드를 표현하는 작업과 구분해서 사용합니다. 값 추가·조회·삭제, 배열 중복 제거와 과일별 개수 집계를 비교해 보겠습니다.
각 절의 버튼 코드는 해당 예제의 HTML 요소가 만들어진 뒤 독립적으로 사용합니다. 다음 공통 함수를 함께 두면 목록은 pre#result, 조회 결과와 안내는 #statusMessage에 표시됩니다. 실행 예제에는 Set·Map·Array.from 지원 여부를 확인해 미지원 환경의 버튼과 입력을 비활성화하는 처리도 있으며, 본문은 지원 환경의 주요 코드를 보여 줍니다.
function show(value) {
document.getElementById('result').textContent = String(value);
}
function message(value) {
document.getElementById('statusMessage').textContent = value;
}
Set 기본 사용
new Set()은 빈 집합을 만들고, 배열을 전달하면 그 값들로 집합을 만듭니다. add(value)는 값을 추가하고 has(value)는 존재 여부를 확인합니다. delete(value)는 값을 제거하며, size로 현재 값의 개수를 읽습니다.
초기 집합은 Apple과 Banana입니다. 입력란 #value의 초기값은 Apple이며 ‘add’ #add, ‘has’ #has, ‘delete’ #delete, ‘초기화’ #reset 버튼으로 조작합니다.
const values = new Set(['Apple', 'Banana']);
const input = document.getElementById('value');
function display() {
show('size: ' + values.size + '\n값 목록: ' + JSON.stringify(Array.from(values)));
}
document.getElementById('add').addEventListener('click', function () {
values.add(input.value);
display();
message('add 완료: 중복 값은 크기를 늘리지 않습니다.');
});
document.getElementById('has').addEventListener('click', function () {
message('has(' + JSON.stringify(input.value) + '): ' + values.has(input.value));
});
document.getElementById('delete').addEventListener('click', function () {
const deleted = values.delete(input.value);
display();
message('delete 결과: ' + deleted);
});
document.getElementById('reset').addEventListener('click', function () {
values.clear();
values.add('Apple');
values.add('Banana');
input.value = 'Apple';
display();
message('준비되었습니다.');
});
display();
처음 표시되는 목록은 다음과 같습니다.
size: 2
값 목록: ["Apple","Banana"]
Apple을 다시 add해도 목록과 size는 그대로입니다. Orange로 바꾸고 add하면 목록 끝에 Orange가 추가되고 size는 3이 됩니다. has는 목록을 바꾸지 않고 상태 안내에 has("Orange"): true처럼 표시합니다.
delete는 실제로 삭제했으면 true, 해당 값이 없으면 false를 반환합니다. 같은 값을 두 번 삭제하면 두 번째 결과는 false입니다. 초기화는 clear()로 집합을 비운 다음 Apple·Banana를 다시 추가하고 입력란도 Apple로 돌립니다.
문자열은 대소문자와 공백을 구분합니다. 이 예제는 입력을 trim()하지 않으므로 Apple, apple, Apple 은 서로 다른 값이며 빈 문자열도 추가할 수 있습니다.
Set으로 배열 중복 제거하기
배열을 Set으로 만든 뒤 Array.from()으로 다시 배열로 변환하면 중복을 제거할 수 있습니다. ‘중복 제거’ 버튼 #run은 Apple과 Banana가 반복되는 원본 배열을 사용합니다.
const original = ['Apple', 'Banana', 'Apple', 'Orange', 'Banana'];
document.getElementById('run').addEventListener('click', function () {
const unique = new Set(original);
const result = Array.from(unique);
show('원본: ' + JSON.stringify(original) + '\nSet size: ' + unique.size
+ '\n배열: ' + JSON.stringify(result) + '\nArray.isArray: ' + Array.isArray(result));
message('처음 등장한 값의 순서를 유지합니다.');
});
show('원본: ' + JSON.stringify(original));
원본: ["Apple","Banana","Apple","Orange","Banana"]
Set size: 3
배열: ["Apple","Banana","Orange"]
Array.isArray: true
원본 배열은 그대로 남고 결과는 새 배열입니다. 값은 처음 등장한 순서대로 남으며 자동 정렬되지는 않습니다. 배열 변환 후에는 배열 메서드로 추가 가공할 수 있습니다.
객체는 내용이 아닌 참조로 구분합니다. 내용이 같아도 따로 만든 두 객체는 서로 다른 값이므로, 객체 배열에 Set을 적용한다고 동일한 id를 가진 객체까지 자동으로 합쳐지지는 않습니다.
Map 기본 사용
new Map()은 빈 Map을 만듭니다. [key, value] 쌍의 배열을 전달하면 초기 항목을 넣을 수 있습니다. set()은 저장·갱신, get()은 값 조회, has()는 key 존재 확인, delete()는 key에 해당하는 항목 삭제에 사용합니다. size는 key/value 쌍의 개수입니다.
초기 항목은 apple → Apple, banana → Banana입니다. 입력란 #key는 apple, #value는 Apple로 시작합니다. ‘set’ #set, ‘get / has’ #read, ‘delete’ #delete, ‘초기화’ #reset 버튼이 있습니다.
const values = new Map([
['apple', 'Apple'],
['banana', 'Banana']
]);
const keyInput = document.getElementById('key');
const valueInput = document.getElementById('value');
function display() {
show('size: ' + values.size + '\nkey/value:\n' + JSON.stringify(Array.from(values.entries()), null, 2));
}
document.getElementById('set').addEventListener('click', function () {
values.set(keyInput.value, valueInput.value);
display();
message('set 완료');
});
document.getElementById('read').addEventListener('click', function () {
message('has: ' + values.has(keyInput.value) + ' | get: ' + String(values.get(keyInput.value)));
});
document.getElementById('delete').addEventListener('click', function () {
const deleted = values.delete(keyInput.value);
display();
message('delete 결과: ' + deleted);
});
document.getElementById('reset').addEventListener('click', function () {
values.clear();
values.set('apple', 'Apple');
values.set('banana', 'Banana');
keyInput.value = 'apple';
valueInput.value = 'Apple';
display();
message('준비되었습니다.');
});
display();
목록에는 size와 Array.from(values.entries())로 만든 key/value 배열이 들여쓰기된 JSON으로 표시됩니다. 초기 size는 2입니다. 초기 입력값으로 ‘get / has’를 누르면 상태 안내에 has: true | get: Apple이 표시되고 목록은 유지됩니다.
key를 apple로 둔 채 value를 Green Apple로 바꾸고 set을 누르면 기존 값만 갱신되며 size는 2입니다. 새 key인 orange를 저장하면 항목이 추가되어 size가 3이 됩니다. 서로 다른 key에 같은 value를 저장하는 것도 가능합니다.
없는 key의 get() 결과는 undefined이고 has()는 false입니다. undefined 자체도 Map의 value로 저장할 수 있으므로, key 존재 여부가 필요하면 get() 결과 대신 has()를 사용합니다. delete()는 삭제했으면 true, key가 없으면 false를 반환합니다.
초기화는 clear() 후 초기 두 항목을 다시 저장하고 입력란도 원래 값으로 되돌립니다. 이 예제의 입력값은 문자열이며 대소문자·공백을 그대로 사용합니다.
객체를 Map의 key로 사용하기
Map의 key는 문자열에 한정되지 않으며 숫자나 객체도 사용할 수 있습니다. 다음은 입력란 예제와 별개로 객체 키를 사용하는 코드입니다. 같은 내용을 가진 새 객체가 아니라 저장할 때 사용한 객체 참조로 조회해야 합니다.
const fruit = {
name: 'Apple'
};
const otherFruit = {
name: 'Apple'
};
const counts = new Map();
counts.set(fruit, 2);
console.log(counts.get(fruit)); // 2
console.log(counts.has(fruit)); // true
console.log(counts.get(otherFruit)); // undefined
console.log(counts.has(otherFruit)); // false
Set과 Map으로 같은 데이터 표현하기
‘같은 데이터로 비교’ 버튼 #run은 ['Apple', 'Banana', 'Apple', 'Orange']를 사용합니다. Set에는 등장한 과일을 한 번씩 담고, Map에는 과일 이름을 key로 하여 등장 횟수를 value로 저장합니다.
const fruits = ['Apple', 'Banana', 'Apple', 'Orange'];
document.getElementById('run').addEventListener('click', function () {
const unique = new Set(fruits);
const counts = new Map();
for (let i = 0; i < fruits.length; i++) {
const fruit = fruits[i];
let count = 0;
if (counts.has(fruit)) {
count = counts.get(fruit);
}
counts.set(fruit, count + 1);
}
show('원본: ' + JSON.stringify(fruits) + '\nSet (고유 값): ' + JSON.stringify(Array.from(unique))
+ '\nMap (값별 개수): ' + JSON.stringify(Array.from(counts.entries())));
message('Set은 값의 존재, Map은 key와 연결된 값을 관리합니다.');
});
show('원본: ' + JSON.stringify(fruits));
원본: ["Apple","Banana","Apple","Orange"]
Set (고유 값): ["Apple","Banana","Orange"]
Map (값별 개수): [["Apple",2],["Banana",1],["Orange",1]]
Set으로는 Apple이 있다는 사실을, Map으로는 Apple이 2번 나왔다는 정보를 다룹니다. Map이 자동으로 개수를 세는 것은 아닙니다. 반복문에서 기존 횟수를 get()으로 읽고 1을 더해 set()으로 갱신합니다. 두 자료구조의 size는 모두 3이며, Map의 value 합계인 4와는 다릅니다.
for...of로 순회하기
Set과 Map은 iterable이므로 for...of로 순회할 수 있습니다. Set은 값을, Map은 [key, value] 쌍을 차례로 제공합니다. 다음 독립 코드는 위 비교 결과와 같은 데이터를 순회합니다.
const fruits = new Set(['Apple', 'Banana', 'Orange']);
for (const fruit of fruits) {
console.log(fruit);
}
const counts = new Map([
['Apple', 2],
['Banana', 1],
['Orange', 1]
]);
for (const entry of counts) {
const key = entry[0];
const value = entry[1];
console.log(key + ': ' + value);
}
둘 다 삽입 순서로 순회합니다. 기존 Map key의 value만 갱신해도 그 key의 순서는 바뀌지 않습니다. 화면에 JSON으로 출력할 때는 실행 예제처럼 Set의 값이나 Map의 entries를 배열로 바꿉니다. 기본 JSON.stringify()가 Set·Map 항목을 자동으로 직렬화해 주지는 않습니다.
Array·Object와 언제 구분해서 쓰는가
| 자료구조 | 선택 기준 | 사용 예 |
|---|---|---|
| Array | 인덱스·순서·중복이 있는 목록을 가공 | 화면 목록, 정렬할 항목 |
| Set | 중복 없는 값과 존재 여부 관리 | 선택된 ID 집합, 고유 태그 |
| Map | 명시적인 key/value 관계와 항목 추가·삭제 관리 | 과일별 개수, 객체별 상태 |
| Object | 정해진 속성으로 일반 데이터 구조 표현 | name·quantity 필드를 가진 과일 데이터 |
일반 객체의 property key는 문자열이나 Symbol입니다. Map은 객체 등을 key로 그대로 쓸 수 있고 set·get·has·delete·size라는 collection API를 제공합니다. Map 자체는 for...of로 순회할 수 있지만, 일반 객체는 Object.entries() 등으로 순회할 항목을 꺼내는 방식이 필요합니다. Set·Map도 삽입 순서를 유지하므로 선택 기준을 단순히 ‘순서가 있는가’만으로 정하지 않습니다.
이 글의 Map은 배열 요소를 변환하는 Array.prototype.map()과는 별개의 자료구조입니다.
주요 메서드와 사용 목적 정리
| 목적 | Set | Map |
|---|---|---|
| 생성 | new Set() | new Map() |
| 추가·갱신 | add(value): 같은 값은 한 번만 보관 | set(key, value): 같은 key의 value 갱신 |
| 존재 확인 | has(value) | has(key) |
| 연결된 값 조회 | 별도 key/value 관계 없음 | get(key) |
| 한 항목 삭제 | delete(value) | delete(key) |
| 전체 삭제 | clear() | clear() |
| 항목 수 | size | size |
고유 값과 존재 여부만 필요하면 Set을, key에 연결된 정보를 함께 관리하려면 Map을 사용합니다. 배열과 변환할 때는 Array.from()으로 각 단계를 드러내면 원본과 결과를 구분하기 쉽습니다.
