맵은 키과 값을 연결한다는 점에서 객체와 비슷하고, 셋은 중복을 허용하지 않는다는 점만 제외하면 배열과 비슷합니다.
맵
Map 객체는 키와 값을 연결할 목적이라면 객체보다 더 좋습니다.
사용자 객체가 여럿 있고 이들에게 각각 역할을 부여한다고 하면
const u1 = {name: 'Cynthia'}
const u2 = {name: 'Jackson'}
const u3 = {name: 'Olive'}
const u4 = {name: 'James'}
먼저 맵을 만듭니다.
const userRoles = new Map()
다음에는 맵의 set()
메서드를 써서 사용자 역할을 할당합니다.
userRoles.set(u1, 'User')
userRoles.set(u2, 'User')
userRoles.set(u3, 'Admin')
set()
메서드는 체인으로 연결할 수 있어서 타이핑하는 수고를 덜어줍니다.
userRoles.set(u1, 'User').set(u2, 'User').set(u3, 'Admin')
생성자에 배열의 배열을 넘기는 형대로 써도 됩니다.
const userRoles = new Map([
[u1, 'User'],
[u2, 'User'],
[u3, 'Admin']
])
이제 u2의 역할을 알아볼 때는 get()
메서드를 쓰면 됩니다.
userRoles.get(u2) // 'User'
맵에 존재하지 않는 키에 get을 호출하면 undefined를 반환합니다. 맵에 키가 존재하는지 확인하는 has()
메서드도있습니다.
userRoles.has(u1) // true
userRoles.get(u1) // 'User'
userRoles.has(u4) // false
userRoles.get(u4) // undefined
맵에 이미 존재하는 키에 set()
을 호출하면 값이 교체됩니다.
userRoles.get(u1) // 'User'
userRoles.set(u1, 'Admin')
userRoles.get(u1) // 'Admin'
size 프로퍼티는 맵의 요소 숫자를 반환합니다.
userRoles.size // 3
keys()
메서드는 맵의 키를, values()
메서드는 값을, entries()
메서드는 첫 번째 요소가 키이고 두 번째 요소가 값인 배열을 각각 반환합니다. 이들 메서드가 반환하는 것은 모두 이터러블 객체이므로 for...of 루프를 쓸 수 있습니다.
for(let u of userRoles.keys()) console.log(u.name)
for(let r of userRoles.values()) console.log(r)
for(let ur of userRoles.entries()) console.log(`${ur[0].name}: ${ur[1]}`)
// 맵도 분해할 수 있습니다.
// 분해하면 좀 더 자연스러운 코드가 됩니다.
for(let [u, r] of userRoles.entries()) console.log(`${ur[0].name}: ${ur[1]}`)
// entries() 메서드는 맵의 기본 이터레이터입니다.
// 위 코드는 다음과 같이 단축해서 쓸 수 있습니다.
for(let [u, r] of userRoles) console.log(`${u.name}: ${r}`)
위크맵
WeakMap
은 다음 차이점을 제외하면 Map과 완전히 같습니다.
- 키는 반드시 객체여야 합니다.
WeakMap
의 키는 가비지 콜렉션에 포함될 수 있습니다.WeakMap
은 이터러블이 아니며claer()
메서드도 없습니다.
일반적으로 자바스크립트는 코드 어딘가에서 객체를 잠조하는 한 그 객체를 메모리에 계속 유지합니다. 예를 들어 Map의 키인 객체 o가 있다면, 자바스크립트는 Map이 존재하는 한 o를 메모리에 계속 유지합니다. WeakMap
에서는 그렇지 않습니다. 따라서 WeakMap
은 이터러블이 될 수 없습니다. 가비지 콜렉션 중인 객체를 노출할 위험이 너무 크기 때문입니다.
WeakMap
의 이런 특징은 객체 인스턴스의 전용 키를 저장하기에 알맞습니다.
const SecretHolder = (function(){
const secrets = new WeakMap()
return class {
setSecret(secret) {
secrets.set(this, secret)
}
get Secret() {
return secrets.get(this)
}
}
})()
앞의 예제에서는 WeakMap
과 그 위크맵을 사용하는 클래스를 함께 IIFE에 넣었습니다. IIFE 외부에서는 그 인스턴스에 비밀스런 내용을 저장할 수 있는 SecretHolder
클래스를 얻게 됩니다. 비밀을 저장할 때는 setScret
메서드를 써야만 하고, 보려 할 때는 getSecret
메서드를 써야만 합니다.
const a = new SecretHolder()
const b = new SecretHolder()
a.setSecret('secret A')
b.setSecret('secret B')
a.getSecret() // 'secret A'
b.getSecret() // 'secret B'
일반적인 Map을 써도 되지만, 그렇게 하면 SecretHolder
인스턴스에 저장한 내용은 가비지 콜렉션에 포함되지 않습니다.
셋
셋은 중복을 허용하지 않는 데이터 집합입니다.
한 사용자에게 여러 역할을 할당하고 싶을 경우에 사용하게 됩니다.
먼저 Set 인스턴스를 만듭니다
const roles = new Set()
이제 사용자 역할을 추가할 때는 add()
메서드를 사용합니다.
roles.add('User') // set['User']
이 사용자에게 관리자 역할을 추가하려면 add()
를 다시 호출합니다.
roles.add('Admin') // set['User', 'Admin']
Map과 마찬가지로 Set에도 size 프로퍼티가 있습니다.
roles.size // 2
셋의 장점은 추가하려는 것이 셋에 이미 있는지 확인할 필요가 없습니다. 이미 있다면 아무 일도 일어나지 않습니다.
roles.add('User') // Set['User', 'Admin']
roles.size // 2
역할을 제거할 때는 delete()
를 호출합니다. 제거에 성공했다면, 즉 그런 역할이 셋에 존재했다면 true
를 반환하고, 그렇지 않다면 false
를 반환합니다.
roles.delete('Admin') // true
roles // Set ['User']
roles.delete('Admin') // false
위크셋
위크겟은 객체만 포함할 수 있으며, 이 객체들은 가비지 콜렉션의 대상이 됩니다. WeakMap
과 마찬가지로 WeakSet
도 이터러블이 아니므로 위크셋의 용도는 매우 적습니다. 위크셋의 실제 용도는 주어진 객체가 셋 안에 존재하는지 아닌지를 알아보는 것뿐이라고 할 수 있습니다.
const naughty = new WeakSet()
const children = [
{name: 'Suzy'},
{name: 'Derek'}
]
naughty.add(children[1])
for(let child of children) {
if(naughty.has(child)) {
console.log(`Coal for ${child.name}!`)
} else {
console.log(`Presents for ${child.name}!`)
}
}