본 포스트는 ponyfoo.com의 ES6 Maps in Depth를 번역한 것입니다. ponyfoo.com의 주인장인 Nicolás의 승인하에 올리는 번역글입니다.
Maps
- 자바스크립트 객체를 이용해서 hash map을 만드는 일반적인 패턴을 대체하기 위해 등장
- key는 숫자, 문자 이외에도 DOM elements나 function 사용 가능
- iterable protocol의 한 종류
new Map()
을 이용해서 map을 생성map.set(key,value)
,map.get(key)
map.has(key)
를 사용해서 해당 key가 map에 있는지 확인map.delete(key)
를 사용해서 엔트리를 제거for (let [key, value] of map)
을 이용해서 map의 엔트리를 순회ES6 Maps in Depth
Before ES6, There Were Hash-Maps
1
2
3
4
5
6
7
8
9
10var registry = {}
function add (name, meta) {
registry[name] = meta
}
function get (name) {
return registry[name]
}
add('contra', { description: 'Asynchronous flow control' })
add('dragula', { description: 'Drag and drop' })
add('woofmark', { description: 'Markdown and WYSIWYG editor' })
자바스크립트는 map을 지원하지 않기 때문에, 위의 소스코드와 같이 문자열을 key로 사용하고, 임의의 값을 value로 사용하게 함으로써 hash map을 구현할 수 있다. 그러나 임시방편으로 생성한 hash map은 몇가지 문제점을 가지고 있다.
- iterable protocol을 구현하는 것이 복잡하다.
- Key가 문자열로 한정되어 있다.
Security issues where user-provided keys like __proto__, toString, or anything in Object.prototype break expectations and make interaction with these kinds of hash-map data structures more cumbersome
이 문장은 아래의 소스코드를 함께 보면 이해하기 쉬울 것 같다. 위의 소스코드에 이어서 작성한 코드이다.1
2
3
4
5
6registry.toString();
# "[object Object]" 가 출력된다.
add('toString', {description:'toString blah blah'});
registry.toString();
# VM356:2 Uncaught TypeError: registry.toString is not a function(…) 가 출력된다.
뭔가 자바스크립트 객체가 가지고 있는 기본 기능들을 사용할 수 없게 되버린다. 앞선 문장의 Security issues
는 이런 경우를 이야기 하는 것 같다.
ES6 Maps
1 | var map = new Map() |
위와 같이 ES6에서 Map
은 key/value
자료 구조로 이루어진다. 이는 hash map보다 훨씬 간단하게 사용 가능하다. 가장 중요한 차이점은 hash map이 문자열만 key로 사용 할 수 있었던 것과 달리, ES6의 Map은 함수, 객체, Date도 Key로 사용 가능하다.1
2
3
4var map = new Map()
map.set(new Date(), function today () {})
map.set(() => 'key', { pony: 'foo' })
map.set(Symbol('items'), [1, 2])
[['key', 'value'], ['key', 'value']]
형태의 collection이거나 iterable protocol은 Map으로 생성할 수 있다.1
2
3
4
5var map = new Map([
[new Date(), function today () {}],
[() => 'key', { pony: 'foo' }],
[Symbol('items'), [1, 2]]
])
위에서 언급한 [['key', 'value'], ['key', 'value']]
형태의 collection이나 iterable protocol은 Map의 생성자를 통해서 쉽게 map으로 변형할 수 있다. Map의 생성자는 내부적으로 아래와 같이 동작한다. forEach
를 이용해서 key/value쌍을 하나씩 map에 할당해주고 있다. (2-dimensional item의 key와 value를 효율적으로 가져오기 위해서 destructuring을 사용했다.) Map constructor를 이용하면 쉽게 key/value
를 쉽게 가져올 수 있다.1
2
3
4
5
6
7var items = [
[new Date(), function today () {}],
[() => 'key', { pony: 'foo' }],
[Symbol('items'), [1, 2]]
]
var map = new Map()
items.forEach(([key, value]) => map.set(key, value))
spread operator도 사용할 수 있다.1
2
3
4
5
6
7var map = new Map()
map.set('p', 'o')
map.set('n', 'y')
map.set('f', 'o')
map.set('o', '!')
console.log([...map])
// <- [['p', 'o'], ['n', 'y'], ['f', 'o'], ['o', '!']]
for of
루프를 사용하여 map을 순회 할 수 있는데, destructuring과 결합하여 map을 간결하게 사용 할 수 있다.1
2
3
4
5
6
7
8var map = new Map()
map.set('p', 'o')
map.set('n', 'y')
map.set('f', 'o')
map.set('o', '!')
for (let [key, value] of map) {
console.log(key, value)
}
api를 추가하기 위해서는 사전에 정의된 api를 통해서 명시적으로 추가해야 하지만, unique한 key의 성질을 보존하기 위해서 key는 자동으로 관리된다. 동일한 key에 값을 넣으면 이전의 값을 덮어쓰게 된다.1
2
3
4
5
6var map = new Map()
map.set('a', 'a')
map.set('a', 'b')
map.set('a', 'c')
console.log([...map])
// <- [['a', 'c']]
ES6 Map에서 NaN
은 corner-case가 된다. key로 사용될 때는 동일하게 인식된다.1
2
3
4
5
6
7console.log(NaN === NaN)
// <- false
var map = new Map()
map.set(NaN, 'foo')
map.set(NaN, 'bar')
console.log([...map])
// <- [[NaN, 'bar']]
Collection Methods in Map
1 | var map = new Map([[NaN, 1], [Symbol(), 2], ['foo', 'bar']]) |
Symbol 값은 항상 다르기 때문에 key로는 사용 할 수 없고, value로만 사용해야 한다.1
2
3
4
5
6
7
8var map = new Map([[1, 'a']])
console.log(map.has(1))
// <- true
console.log(map.has('1'))
// <- false
// example of key casting
var a = {}
a['1'] === a[1]
map은 key casting을 하지 않는다.1
2
3
4
5
6var map = new Map([[1, 2], [3, 4], [5, 6]])
map.clear()
console.log(map.has(1))
// <- false
console.log([...map])
// <- []
clear()
는 map을 초기화 시키기 위해서 사용할 수 있다.1
2
3
4
5var map = new Map([[1, 2], [3, 4], [5, 6]])
console.log([...map.keys()])
// <- [1, 3, 5]
console.log([...map.values()])
// <- [2, 4, 6]
keys()
와 values()
는 각각 map의 모든 key와 value를 반환한다. 결과의 형태는 Iterator object 이다.1
2
3
4
5
6
7
8
9var map = new Map([[1, 2], [3, 4], [5, 6]])
console.log(map.size)
// <- 3
map.delete(3)
console.log(map.size)
// <- 2
map.clear()
console.log(map.size)
// <- 0
delete()
를 통해서 map의 요소를 삭제할 수 있다.1
2
3
4
5var map = new Map([[NaN, 1], [Symbol(), 2], ['foo', 'bar']])
map.forEach((value, key) => console.log(key, value))
// <- NaN 1
// <- Symbol() 2
// <- 'foo' 'bar'
위 소스코드는 for of
와 동일한 동작을 한다.
References
- https://ponyfoo.com/articles/es6-maps-in-depth 기반으로 번역
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
- https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols
- https://ponyfoo.com/articles/es6
- http://honeybunnys.blog.me/220438148417
- https://leanpub.com/understandinges6/read#leanpub-auto-sets-and-maps-in-ecmascript-5
Comments