ES6에 새로 추가된 Map 타입의 기본 문법과 특징을 간단하게 알아보겠습니다.

Mapkeyvalue[key, value] 형식으로 이루어진 리스트입니다.

Map 생성 및 요소 추가

Map 객체 생성은 다음과 같이 new Map()으로 하며 set으로 요소를 추가할 수 있습니다.

const m1 = new Map([['a', 1], ['b', 2]])  // map 생성

console.log(m1)  // Map(2) {"a" => 1, "b" => 2}


const m2 = new Map()  // 빈 맵 추가
m.set('a', 1)  // a가 key, 1이 value로 요소 추가
m.set('b', 2)  // a가 key, 1이 value로 요소 추가

console.log(m2)  // Map(2) {"a" => 1, "b" => 2}

String, Symbol 외 타입을 키로 사용 가능

Object는 StringSymbol 타입만 키로 사용이 가능합니다.

따라서 다음과 같은 코드는 오류가 발생합니다.

    const obj1 = { a => a: 1 }  // SyntaxError: Unexpected token =>
    const obj2 = { {}: 1 }  // SyntaxError: Unexpected token {

반면, Map은 StringSymbol 이외의 타입을 키로 사용 가능합니다.

키를 원시 타입으로 사용할 수도 있고, 심지어는 함수나 객체도 키로 사용할 수 있습니다.

const m = new Map()
m.set(a => a, 1)  // 키로 함수 사용
m.set({}, 2)  // 키로 빈 객체 사용
m.set(false, true)  // 키로 불린 사용

console.log(m)  // Map(3) {ƒ => 1, {…} => 2, false => true}

Map에서 요소 삭제

요소 삭제는 delete 메소드를 이용합니다.

delete는 키를 전달인자로 받으며, 해당 키의 요소가 있으면 삭제하고 true를, 없으면 false를 반환합니다.

주의할 점은 함수나 객체를 키로 사용할 경우 참조 관계 때문에 예상대로 동작하지 않을 수 있습니다.

따라서 직접 참조하는 값을 전달인자로 사용해야 합니다.

const keyObj = {}
const keyFunc = a => a

const m2 = new Map([['a', 1], ['b', 2], [keyObj, 3], [keyFunc, 4]])

m2.delete('c')  // c라는 키가 없으므로 false
m2.delete('a')  // a라는 키의 요소를 삭제하고 true
console.log(m2)  // Map(3) {"b" => 2, {…} => 3, ƒ => 4}

m2.delete({})  // false. 객체는 참조 타입
m2.delete(a => a)  // false. 함수도 객체이므로 참조 타입

m2.delete(keyObj)  // true. 키가 객체인 요소 삭제
m2.delete(keyFunc)  // true. 키가 함수인 요소 삭제

console.log(m2)  // Map(1) {"b" => 2}

iterable 프로토콜 지원

Map은 기본적으로 Symbol.iterator을 가지고 있습니다. 즉 iterable 프로토콜을 지원한다는 말이고, 이는 객체와는 달리 for of 문으로 직접 순회할 수 있다는 의미입니다.

const m3 = new Map([['a', 1], ['b', 2], ['c', 3]])

const obj3 = {a: 1, b: 2, c: 3}

console.log(obj3[Symbol.iterator])  // undefined. 기본 iterable 지원 안함

for (let i of m3) {
  console.log(i)  // ['a', 1], ['b', 2], ['c', 3] 이 순차적으로 출력됨
}

Map이 가지는 메소드들

이미 Map에서 요소를 삭제할 때 setdelete를 사용했습니다.

이 두가지 외에도 Map에는 유용한 여러 메소드들이 있습니다.

clear

모든 요소를 제거하고 빈 Map으로 만듭니다.

const m4 = new Map([['a', 1], ['b', 2], ['c', 3]])

console.log(m4)  // Map(3) {"a" => 1, "b" => 2, "c" => 3}

m4.clear()

console.log(m4)  // Map(0) {}

entries

[key, value] 요쇼를 가진 배열의 Iterator 객체를 반환합니다.

const m5 = new Map([['d', 4], ['e', 5], ['f', 6]])

const iter = m5.entries()

console.log(iter.next())  // {value: ["d", 4], done: false}
console.log(iter.next())  // {value: ["e", 5], done: false}
console.log(iter.next())  // {value: ["f", 6], done: false}
console.log(iter.next())  // {value: undefined, done: true}

forEach

배열의 forEach랑 비슷합니다. value, key를 파라미터로 사용 가능합니다.

const m6 = new Map([[_ => _, 'a'], [{}, 'e'], [[1, 2, 3], 4]])

m6.forEach((value, key) => {
  console.log(value)  // 순차적으로 'a', 'e', 4 출력
  console.log(key)  // 순차적으로 _=>_, {}, [1, 2, 3] 출력
})

get

키를 전달인자로 받아서 해당 key의 요소가 존재하면 value를, 없으면 undefined를 반환합니다.

마찬가지로 참조타입을 key로 사용할 땐 주의해야 합니다.

const keyObj = {}

const m7 = new Map([['a', 1], [keyObj, 2]])

console.log(m7.get('a'))  // 1
console.log(m7.get('c'))  // undefined

console.log(m7.get({}))  // undefined. 객체는 참조 타입
console.log(m7.get(keyObj))  // 2

has

키를 전달인자로 받아서 존재하면 true, 없으면 false를 반환합니다.

마찬가지로 참조타입을 key로 사용하면 주의해야겠죠?

const m8 = new Map([['z', 5], ['x', 7]])

console.log(m8.has('y'))  // false
console.log(m8.has('x'))  // true

keys

key로만 이루어진 Iterator 객체를 반환합니다.

const m9 = new Map([['aa', 4], ['bb', 5], ['cc', 6]])

const iter = m9.keys()

console.log(iter.next())  // {value: "aa", done: false}
console.log(iter.next())  // {value: "bb"], done: false}
console.log(iter.next())  // {value: "cc", done: false}
console.log(iter.next())  // {value: undefined, done: true}

values

value로만 이루어진 Iterator 객체를 반환합니다.

const m10 = new Map([[1, 'v1'], [2, 'v2'], [3, 'v3']])

const iter = m10.keys()

console.log(iter.next())  // {value: "v1", done: false}
console.log(iter.next())  // {value: "v2"], done: false}
console.log(iter.next())  // {value: "v3", done: false}
console.log(iter.next())  // {value: undefined, done: true}

size

size는 메소드는 아닙니다. Map의 총 길이를 반환하는 속성입니다.

const m11 = new Map([[1, 'v1'], [2, 'v2'], [3, 'v3'], [4, 'v4']])

console.log(m11.size)  // 4

Object와 차이점

Ojbect와 차이점을 정리하면 다음과 같습니다.

  • MapObject와 달리 삽입순으로 순서가 보장됩니다.
  • Map은 기본 size 속성으로 쉽게 길이를 얻을 수 있는데, Object는 기본으론 못하고 따로 함수를 만들어야 합니다.
  • Map의 키는 Object와 달리 key로 어떤 타입이든 사용 가능한 반면, ObjectSymbolString만 가능합니다.
  • Mapiterable 프로토콜을 지원해서 바로 순회가 가능합니다.

생각해볼 것

  • Map 타입을 활용할 수 있는 쓰임새
  • Object와 비교해서 더 좋게 사용할 수 있는 상황