Set 객체는 중복되지 않는 값들의 집합을 표현한다. 배열과 달리 같은 값을 여러 번 저장할 수 없으며, 값의 순서에도 큰 의미가 없다. 예를 들어 new Set()으로 빈 Set을 만들 수 있고, [1, 2, 3]과 같은 배열을 인수로 전달하면 그 값을 초기값으로 갖는 Set을 생성할 수 있다.
const set = new Set([1, 2, 3, 3]);
console.log(set); // Set(3) {1, 2, 3}
위 예시처럼 중복된 값은 자동으로 제거된다. 즉, Set은 데이터의 유일성 보장을 위해 주로 사용된다.
Set 객체는 new Set() 생성자 함수로 만든다. 인수를 전달하지 않으면 빈 Set이 되고, 배열이나 이터러블 객체를 전달하면 그 요소들을 자동으로 Set의 값으로 저장한다. 이때 중복 요소는 자동으로 걸러진다.
Set의 요소 개수를 확인하려면 size 프로퍼티를 사용한다. 이건 배열의 length처럼 보이지만, 함수가 아니라 getter로만 제공된다. 즉, set.size = 10 같은 식으로 임의로 바꾸는 건 불가능하다.
add 메서드를 이용하면 Set에 요소를 추가할 수 있다. 이미 존재하는 값이라면 아무 일도 일어나지 않으며, 중복은 자동으로 방지된다.
const set = new Set();
set.add(1);
set.add(2);
set.add(2); // 무시됨
console.log(set); // Set(2) {1, 2}
has 메서드를 사용하면 특정 값이 Set에 존재하는지 확인할 수 있다. 결과는 true 또는 false로 반환된다.
set.has(1); // true
set.has(3); // false
이 메서드는 배열의 includes와 비슷한 역할을 하지만, Set의 내부 구조는 해시 기반이라 훨씬 빠르다.
delete 메서드를 사용하면 특정 값을 제거할 수 있다. 삭제에 성공하면 true, 존재하지 않으면 false를 반환한다.
set.delete(2);
console.log(set); // Set(1) {1}
모든 요소를 한 번에 삭제할 때는 clear() 메서드를 사용한다. 이 메서드를 호출하면 Set이 완전히 비워진다.
set.clear();
console.log(set.size); // 0
Set은 forEach 메서드나 for...of 문으로 순회할 수 있다. 이때 콜백 함수의 첫 번째 인수와 두 번째 인수는 둘 다 같은 값이다. Set에는 key가 없기 때문이다. 따라서 아래 두 코드는 동일하게 동작한다.
set.forEach((value, _, set) => console.log(value));
for (const value of set) console.log(value);
또한 keys(), values(), entries() 메서드도 존재하지만, Set에서는 모두 동일한 결과를 반환한다.
Set은 수학적인 집합 연산에도 자주 사용된다. 배열처럼 구조분해할 수 있기 때문에, 전개 연산자(...)와 배열 메서드를 조합하면 교집합, 합집합, 차집합 등을 구현할 수 있다.
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
// 교집합
const intersection = new Set([...a].filter(v => b.has(v))); // {2, 3}
// 합집합
const union = new Set([...a, ...b]); // {1, 2, 3, 4}
// 차집합
const difference = new Set([...a].filter(v => !b.has(v))); // {1}
// 부분집합
const isSubset = [...a].every(v => b.has(v)); // false
이처럼 Set은 데이터 중복 제거뿐 아니라 집합 연산 로직을 구현할 때 매우 유용하다.
Map 객체는 키와 값의 쌍으로 데이터를 저장하는 구조다. 객체({})와 비슷하지만, 몇 가지 중요한 차이점이 있다. 객체의 키는 문자열이나 심볼만 가능하지만, Map의 키는 모든 자료형(문자열, 숫자, 객체, 배열 등)을 사용할 수 있다.
new Map()으로 Map을 생성할 수 있으며, [key, value] 형태의 배열을 전달해 초기값으로 설정할 수도 있다.
const map = new Map([
['name', 'Hyojung'],
['age', 26],
]);
이 경우 name이라는 키에 'Hyojung'이라는 값이, age라는 키에 26이 저장된다.
Map의 크기를 알고 싶을 때는 map.size를 사용한다. 이 역시 Set과 마찬가지로 읽기 전용 프로퍼티이며, 수동으로 변경할 수 없다.
새로운 키와 값을 추가하려면 set(key, value) 메서드를 사용한다. 이미 존재하는 키에 값을 설정하면, 기존 값이 덮어씌워진다.
const map = new Map();
map.set('colour', 'blue');
map.set('colour', 'red');
console.log(map.get('colour')); // 'red'
set 메서드도 자기 자신을 반환하므로 체이닝이 가능하다.
get(key) 메서드는 주어진 키에 해당하는 값을 반환한다. 만약 존재하지 않으면 undefined를 반환한다.
map.get('colour'); // 'red'
map.get('size'); // undefined
특정 키가 존재하는지 확인하려면 has(key) 메서드를 사용한다. 반환값은 true 또는 false다.
map.has('colour'); // true
delete(key) 메서드는 해당 키를 제거하고, 성공 여부를 boolean 값으로 반환한다.
모든 데이터를 한 번에 제거하려면 clear() 메서드를 사용한다.
Map은 삽입 순서를 유지하며 순회한다. forEach나 for...of를 사용할 수 있다.
map.forEach((value, key) => console.log(key, value));
for (const [key, value] of map) {
console.log(key, value);
}
keys(), values(), entries() 메서드를 사용하면 각각 키 목록, 값 목록, 키-값 쌍을 순회할 수도 있다. 이 덕분에 Map은 객체보다 구조적이고 반복 가능한 데이터 관리에 적합하다.
Set은 값 중심의 집합, Map은 키-값 중심의 데이터 저장소라고 정리할 수 있다. 둘 다 JavaScript에서 데이터 구조를 보다 명확하게 표현할 수 있는 고급 컬렉션 객체로, 배열이나 객체보다 명확한 목적이 있을 때 더 효율적으로 사용할 수 있다.