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()으로 각 단계를 드러내면 원본과 결과를 구분하기 쉽습니다.

참고자료