본문 바로가기

Front-End: Web/JavaScript

[js] Map이란?

반응형

Map

  • Map 은 객체 형태다.
  • key-value 쌍과, 키의 삽입 순서를 기억한다.
  • 모든 값을 키로 사용할 수 있다. (그냥 객체는 String, Symbol만 가능)
  • 키는 unique한 값이다.
  • for...of 루프를 돌면 [key, value] 로 이뤄진 배열을 반환한다. 삽입된 순서대로 반복한다. (set을 한 순서대로)
  • 키 동일성은 SameValueZero를 기반으로 한다.
    • 0 ≠ -0
    • Nan === Nan
  • 객체 vs 맵
    • 둘이 유사하다.
    • 맵은 아이템 수를 size 로 쉽게 가져올 수 있지만, 객체는 일일히 봐야함
    • 맵은 순회가능(iterator)하므로 for…of문 사용 가능하지만, 객체는 아니다.
    • 맵이 객체보다 성능이 더 좋다. (키-값 쌍이 빈번하게 추가/제거되는 상황에 최적화되어 있음)

 

메서드

  • set(key, value): 값 넣기
  • get(key): value 가져오기
  • size: 크기 가져오기
  • clear(): 모든 값(키-값 쌍)을 제거
  • delete(key): key를 가진 요소 제거. 잘 제거 되었으면 true를 반환하고, 그렇지 않으면 false를 반환한다.
  • has(key): key를 가지고 있는지 boolean값 반환. delete()후 has()를 살펴보면 false가 뜬다.
  • @iterator(): 삽입된 순서대로 [key, value] 두 배열을 포함하는 새로운 반복자(iterator)를 반환한다.
  • keys(): 삽입된 순서대로 모든 키들을 포함하는 새로운 반복자(iterator)를 반환한다.
  • values(): 삽입된 순서대로 모든 값들을 포함하는 새로운 반복자(Iterator)를 반환한다.
  • entries(): 삽입된 순서대로 모든 키, 값들을 포함하는 새로운 반복자(iterator)를 반환한다.
  • forEach(): 각 키-값 쌍에 대해 삽입된 순서대로 callbackFn을 한 번씩 호출한다.

 

 

예제

간단한 예제

const map1 = new Map();

map1.set('a', 1);
map1.set('b', 2);
map1.set('c', 3);

console.log(map1.get('a')); // 1

map1.set('a', 97);

console.log(map1,get('a')); // 97

console.log(map1.size); // 3

map1.delete('b');
console.log(map1.size); // 2

 

이렇게 사용하면 안돼요!

객체와 비슷하게 생겼다고 이렇게 꺼내서 값을 넣으면(?) 안된다. 무조건 set(key, value)로만 넣어야 한다.

const wrongMap = new Map();
**wrongMap['bla'] = 'blaa';
wrongMap['bla2'] = 'blaa2';**

console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaa2' }

wrongMap.has('bla'); // false (실패)
wrongMap.delete('bla'); // false (실패)

console.log(wrongMap); /// Map {bla: 'blaa', bla2: 'blaa2' }

콘솔로 찍었을 때 넣은 값들이 잘 나오니까 동작도 잘할 것처럼 보이지만,

has, delete 와 같이 메서드들을 사용하니 전혀 동작하지 않고, Map 데이터 구조와 상호작용하지 않는 모습을 보여준다.

속성 설정은 일반 객체의 기능을 사용한다. 그래서 ‘bla’ 값은 Map에 저장되지 않는다.

 

다른 예제 - Map 객체 사용하기

coonst myMap = new Map();

const keyString = 'a string';
const keyObj = {};
const keyFunc = function() {};

// setting value
myMap.set(keyString, "value associated with a string");
myMap.set(keyObj, "value associated with keyObj");
myMap.set(keyFunc, "value associated with keyFunc");

console.log(myMap.size); // 3

// get value
console.log(myMap.get(keyString)); // "value associated with a string"
console.log(myMap.get(keyObj)); // "value associated with keyObj"
console.log(myMap.get(keyFunc)); // "value associated with keyFunc"

console.log(myMap.get('a string')); // "value associated with a string"
// keyString === 'a string' (primitive type)
console.log(myMap.get({}); // undefined.
// keyObj !== {} (reference type)
console.log(myMap.get(function() {})); // undefined.
// keyFunc !== function() {} (reference type)

 

다른 예제 - Map 키로 NaN 사용하기

원래는 모든 NaN이 동일하지 않지만, NaN은 서로 구별할 수 없으므로 동일한 키로 간주된다.

const myMap = new Map();
myMap.set(NaN, 'not a number');

myMap.get(NaN); // 'not a number'

const otherNaN = Number('foo');
myMap.get(otherNaN); // 'not a number'

 

다른 예제 - for...of로 맵 순회하기

const myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');

for (const [key, value] of myMap) {
	console.log(`${key} = ${value}`);
}
// 0 = zero
// 1 = one

for (const key of myMap.keys()) {
	console.log(key);
}
// 0
// 1

for (const value of myMap.values()) {
	console.log(value);
}
// zero
//one

for (const [key, value] of myMap.entries) {
	console.log(`${key} = ${value}`);
}
// 0 = zero
// 1 = one

 

다른 예제 - forEach((value, key)) 로 맵 순회하기

맵은 Iterator하기 때문에 forEach로도 순회할 수 있다.

myMap.forEach((value, key) => {
	console.log(`${key} = ${value}`);
});
// 0 = zero
// 1 = one

 

 

배열과 맵

2차열의 Key-value 배열을 Map으로, Map을 배열로 바꿀 수 있다.

  • Map 생성자 사용하기
const kvArray = [['key1', 'value1'], ['key2', 'value2']];

**const myMap = new Map(kvArray);**

myMap.get('key1'); // "value1"
  • Array.prototype.from() 사용하기
**Array.from(myMap)**; // { key1: "value1", key2: "value2" }
  • spread operator 사용하기 (맵을 배열로 변환한다)
**[...myMap];** // kvArray와 동일
  • keys(), values() iterators를 사용하기
**Array.from(myMap.keys())**; // ["key1", "key2"]

 

맵 복제하기

Array와 동일하게 복제가 가능하다. 하지만 Reference Type이므로 원본과 복제본이 동일하진 않다.

const original = new Map([
	[1, 'one'],
]);

const clone = new Map(original);

console.log(clone.get(1)); // one
console.log(original === clone); // false

 

맵 합치기(병합)

  • 병합시, 키가 중복되면 마지막 키의 값을 따른다.
  • spread operator는 맵을 배열로 변환한다.
const first = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

const second = new Map([
  [1, 'uno'],
  [2, 'dos'],
]);

const merged = new Map([...first, ...second]);

merged.get(1); // uno
merged.get(2); // dos
merged.get(3); // three
  • 맵과 배열이 함께 병합할 수도 있다.
const merged = new Map([...first, ...second, [1, 'eins']]);

merged.get(1); // ein 

 

 

Map.prototype@@iterator

  • @@iterator 메서드는 반복 가능하도록 한다.
  • 그래서 맵이 spread operator와 for ... of 같이 루프 반복이 필요한 구문에서 사용될 수 있도록 한다.
  • 키-값 쌍을 생성하는 반복자를 반환한다. (즉, Map.prototype.entries 와 동일한 값을 반환함)

 

예제 - for of

const map1 = new Map();

map1.set('0', 'foo');
map1.set(1, 'bar');

const iterator1 = map1[Symbol.iterator]();

for (const item of iterator1) {
  console.log(item);
}
// ["0", "foo"]
// [1, "bar"]

 

예제 - for [key, value] of

const myMap = new Map();
myMap.set("0", "foo");
myMap.set(1, "bar");
myMap.set({}, "baz");

for (const entry of myMap) {
  console.log(entry);
}
// ["0", "foo"]
// [1, "bar"]
// [{}, "baz"]

for (const [key, value] of myMap) {
  console.log(`${key}: ${value}`);
}
// 0: foo
// 1: bar
// [Object]: baz 

 

예제 - 수동으로 반복자 돌리기: next()

next() 메서드를 사용하면, 수동으로 다음 요소를 호출할 수 있다.

이렇게 하면 반복 프로세스를 최대로 제어할 수 있다.

const myMap = new Map();
myMap.set("0", "foo");
myMap.set(1, "bar");
myMap.set({}, "baz");

const mapIter = myMap[Symbol.iterator]();

console.log(mapIter.next().value); // ["0", "foo"]
console.log(mapIter.next().value); // [1, "bar"]
console.log(mapIter.next().value); // [Object, "baz"]
반응형