JavaScript์์ ํค-๊ฐ ์์ ์ ์ฅํ ๋, ๋๋ถ๋ถ {} ๊ฐ์ฒด๋ฅผ ์๋๋ค. ๊ทธ๋ฐ๋ฐ ES6์์ Map์ด ์ถ๊ฐ๋์์ต๋๋ค. ์ Object๊ฐ ์๋๋ฐ Map์ ๋ฐ๋ก ๋ง๋ค์์๊น์? "๋ ๋ค ํค-๊ฐ ์ ์ฅ์ ์๋์ผ?"๋ผ๊ณ ์๊ฐํ๊ธฐ ์ฝ์ง๋ง, ๋ด๋ถ ๊ตฌ์กฐ๋ถํฐ ์ฉ๋๊น์ง ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฆ
๋๋ค.
Object๋ ์๋ ํค-๊ฐ ์ ์ฅ์๋ก ์ค๊ณ๋ ๊ฒ์ด ์๋๋๋ค. ํ๋กํผํฐ์ ์งํฉ์ ๋๋ค. ๋ฉ์๋๋ฅผ ๋ด๊ณ , ํ๋กํ ํ์ ์ฒด์ธ์ ํตํด ์์์ ๊ตฌํํ๋ ๊ฒ์ด ๋ณธ๋ ๋ชฉ์ ์ ๋๋ค.
const user = {
name: 'Kim',
greet() {
return `Hello, ${this.name}`;
},
};
// user๋ "๋ฐ์ดํฐ ์ ์ฅ์"๊ฐ ์๋๋ผ "์ํฐํฐ"์
๋๋ค.
// ํ๋กํผํฐ(name)์ ํ์(greet)๋ฅผ ํจ๊ป ๊ฐ์ง๋๋ค.
์ด ๊ตฌ์กฐ๋ฅผ ํค-๊ฐ ์ ์ฅ์๋ก ์ฐ๋ฉด ์ฌ๋ฌ ๋ฌธ์ ๊ฐ ์๊น๋๋ค.
const map = {};
map['toString'] = 'hello';
// ์๋: 'toString'์ด๋ผ๋ ํค์ 'hello'๋ฅผ ์ ์ฅ
// ์ค์ : Object.prototype.toString์ ๋ฎ์ด์
typeof map.toString; // 'string' (ํจ์๊ฐ ์๋๋ผ ๋ฌธ์์ด)
Object.create(null)๋ก ํ๋กํ ํ์
์๋ ๊ฐ์ฒด๋ฅผ ๋ง๋ค๋ฉด ์ด ๋ฌธ์ ๋ฅผ ํผํ ์ ์์ง๋ง, ๊ทธ๊ฑด Object์ ํต์ฌ ๊ธฐ๋ฅ(ํ๋กํ ํ์
์ฒด์ธ)์ ํฌ๊ธฐํ๋ ๊ฒ์
๋๋ค. ํค-๊ฐ ์ ์ฅ์๋ก ์ฐ๊ธฐ ์ํด Object์ ๋ณธ์ง์ ๋ถ์ ํ๋ ์
์
๋๋ค.
Object์ ํค๋ ๋ฌธ์์ด ๋๋ Symbol๋ง ๊ฐ๋ฅํฉ๋๋ค.
const obj = {};
const key1 = { id: 1 };
const key2 = { id: 2 };
obj[key1] = 'first';
obj[key2] = 'second';
console.log(obj[key1]); // 'second' โ 'first'๊ฐ ์๋๋๋ค
console.log(Object.keys(obj)); // ['[object Object]']
// key1๊ณผ key2 ๋ชจ๋ '[object Object]'๋ก ๋ณํ๋์ด ๊ฐ์ ํค๊ฐ ๋ฉ๋๋ค.
์ซ์๋ฅผ ํค๋ก ์จ๋ ๋ด๋ถ์ ์ผ๋ก ๋ฌธ์์ด๋ก ๋ณํ๋ฉ๋๋ค.
const obj = {};
obj[1] = 'one';
obj['1'] = 'one string';
console.log(obj[1]); // 'one string'
// ์ซ์ 1๊ณผ ๋ฌธ์์ด '1'์ด ๊ฐ์ ํค์
๋๋ค.
Map์ ์ฒ์๋ถํฐ ํค-๊ฐ ์ ์ฅ์๋ก ์ค๊ณ๋์์ต๋๋ค. Object์ ํค-๊ฐ ์ฉ๋ ํ๊ณ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ES6์์ ์ถ๊ฐ๋์์ต๋๋ค.
const map = new Map();
// ์ด๋ค ํ์
์ด๋ ํค๋ก ์ฌ์ฉ ๊ฐ๋ฅ
map.set(1, 'number key');
map.set('1', 'string key');
map.set(true, 'boolean key');
map.set(null, 'null key');
map.set({ id: 1 }, 'object key');
map.set(document.body, 'DOM element key');
console.log(map.get(1)); // 'number key'
console.log(map.get('1')); // 'string key' โ ๊ตฌ๋ถ๋ฉ๋๋ค
Map์ ํค ๋น๊ต๋ SameValueZero ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํฉ๋๋ค. ===์ ๊ฑฐ์ ๊ฐ์ง๋ง, NaN === NaN์ true๋ก ์ฒ๋ฆฌํฉ๋๋ค.
const map = new Map();
map.set(NaN, 'not a number');
console.log(map.get(NaN)); // 'not a number'
// === ์์๋ NaN !== NaN์ด์ง๋ง, Map์์๋ ๊ฐ์ ํค๋ก ์ทจ๊ธํฉ๋๋ค.
map.set(0, 'zero');
map.set(-0, 'negative zero');
console.log(map.get(0)); // 'negative zero'
// 0๊ณผ -0๋ ๊ฐ์ ํค๋ก ์ทจ๊ธํฉ๋๋ค.
๋จ, ๊ฐ์ฒด ํค๋ ์ฐธ์กฐ ๋์ผ์ฑ์ผ๋ก ๋น๊ตํฉ๋๋ค.
const map = new Map();
map.set({ id: 1 }, 'first');
console.log(map.get({ id: 1 })); // undefined
// ๋ด์ฉ์ด ๊ฐ์๋ ๋ค๋ฅธ ์ฐธ์กฐ์ด๋ฏ๋ก ๋ค๋ฅธ ํค์
๋๋ค.
const key = { id: 1 };
map.set(key, 'found');
console.log(map.get(key)); // 'found'
// ๊ฐ์ ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
| Object | Map | |
|---|---|---|
| ํค ํ์ | ๋ฌธ์์ด, Symbol | ๋ชจ๋ ๊ฐ (๊ฐ์ฒด, ํจ์, ์์๊ฐ) |
| ํค ์์ | ์ ์ ํค โ ๋ฌธ์์ด ํค(์ฝ์ ์) โ Symbol ํค | ํญ์ ์ฝ์ ์์ ๋ณด์ฅ |
| ํฌ๊ธฐ ํ์ธ | Object.keys(obj).length (O(n)) | map.size (O(1)) |
| ๋ฐ๋ณต | Object.keys() / for...in | for...of / forEach (์ง์ iterable) |
| ํ๋กํ ํ์ | ์์ (ํค ์ถฉ๋ ๊ฐ๋ฅ) | ์์ (์์ ์ ์ฅ์) |
| ์ง๋ ฌํ | JSON.stringify() ๋ค์ดํฐ๋ธ ์ง์ | ์ง์ ๋ณํ ํ์ |
| ์ฑ๋ฅ (๋น๋ฒํ ์ถ๊ฐ/์ญ์ ) | ๋๋ฆผ | ๋น ๋ฆ |
Object์ ํค ์์๋ ์ง๊ด์ ์ด์ง ์์ต๋๋ค. ECMAScript ์คํ์์ ์ ์๋ ๊ท์น์ด ์์ต๋๋ค.
const obj = {};
obj['b'] = 2;
obj['1'] = 1;
obj['a'] = 3;
obj['2'] = 4;
obj[Symbol('s')] = 5;
console.log(Object.keys(obj));
// ['1', '2', 'b', 'a']
// ์ ์ ํค๊ฐ ๋จผ์ (์ค๋ฆ์ฐจ์) โ ๋ฌธ์์ด ํค(์ฝ์
์) โ Symbol(Object.keys์๋ ์ ๋์ด)
Map์ ๋จ์ํฉ๋๋ค. ํญ์ ์ฝ์ ์์์ ๋๋ค.
const map = new Map();
map.set('b', 2);
map.set(1, 1);
map.set('a', 3);
map.set(2, 4);
console.log([...map.keys()]); // ['b', 1, 'a', 2] โ ๋ฃ์ ์์ ๊ทธ๋๋ก
Object๋ ๊ธฐ๋ณธ์ ์ผ๋ก iterable์ด ์๋๋๋ค. for...of๋ฅผ ์ง์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
const obj = { a: 1, b: 2, c: 3 };
// โ TypeError: obj is not iterable
for (const [key, value] of obj) { }
// Object๋ฅผ ์ํํ๋ ค๋ฉด ๋ณํ์ด ํ์ํฉ๋๋ค
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}
// ๋๋ for...in (ํ๋กํ ํ์
์ฒด์ธ์ ํ๋กํผํฐ๋ ํฌํจ๋ ์ ์์)
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key]);
}
}
Map์ iterable protocol์ ๊ตฌํํ๊ณ ์์ด์ ์ง์ ์ํํ ์ ์์ต๋๋ค.
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
// โ
๋ฐ๋ก ์ฌ์ฉ ๊ฐ๋ฅ
for (const [key, value] of map) {
console.log(key, value);
}
// ๊ตฌ์กฐ ๋ถํด, ์คํ๋ ๋, Array.from ๋ชจ๋ ๊ฐ๋ฅ
const entries = [...map]; // [['a', 1], ['b', 2], ['c', 3]]
const keys = [...map.keys()]; // ['a', 'b', 'c']
const values = [...map.values()]; // [1, 2, 3]
// forEach๋ ์ง์ (์ฝ๋ฐฑ ์ธ์ ์์ ์ฃผ์: value, key)
map.forEach((value, key) => {
console.log(key, value);
});
V8 ์์ง์ Object๋ฅผ Hidden Class(๋ด๋ถ์ ์ผ๋ก Shape ๋๋ Structure๋ผ๊ณ ๋ถ๋ฆ ๋๋ค)๋ก ์ต์ ํํฉ๋๋ค. ๊ฐ์ ๊ตฌ์กฐ์ ๊ฐ์ฒด๋ฅผ ๋ฐ๋ณต ์์ฑํ๋ฉด ๊ฐ์ Hidden Class๋ฅผ ๊ณต์ ํ๋ฉฐ, ํ๋กํผํฐ ์ ๊ทผ์ด ์ ์ ์คํ์ ๊ณ์ฐ์ผ๋ก ์ด๋ฃจ์ด์ ธ ๋งค์ฐ ๋น ๋ฆ ๋๋ค.
// V8์ด Hidden Class๋ฅผ ๋ง๋ค์ด ์ต์ ํํ๋ ํจํด
function createUser(name, age) {
return { name, age }; // ํญ์ ๊ฐ์ ๊ตฌ์กฐ โ ๊ฐ์ Hidden Class
}
// ์ด๋ ๊ฒ ์ฐ๋ฉด ์ ์ ์ปดํ์ผ ์ธ์ด ์์ค์ ํ๋กํผํฐ ์ ๊ทผ ์๋
const user = createUser('Kim', 30);
console.log(user.name); // Hidden Class ๋๋ถ์ ์คํ์
๊ณ์ฐ์ผ๋ก ์ฆ์ ์ ๊ทผ
ํ์ง๋ง Object๋ฅผ ํค-๊ฐ ์ ์ฅ์์ฒ๋ผ ๋์ ์ผ๋ก ํ๋กํผํฐ๋ฅผ ์ถ๊ฐ/์ญ์ ํ๋ฉด Hidden Class ์ต์ ํ๊ฐ ๊นจ์ง๋๋ค. V8์ ์ด๋ฐ Object๋ฅผ "dictionary mode"๋ก ์ ํํ๋๋ฐ, ์ด ๋ชจ๋์์๋ ํด์ ํ ์ด๋ธ์ ์ฌ์ฉํฉ๋๋ค.
const cache = {};
// ํ๋กํผํฐ๋ฅผ ๊ณ์ ์ถ๊ฐ/์ญ์ ํ๋ฉด dictionary mode๋ก ์ ํ
for (let i = 0; i < 10000; i++) {
cache[`key_${i}`] = i;
}
// delete ์ฐ์ฐ์ Hidden Class๋ฅผ ๊นจ๋จ๋ฆฌ๋ ๋ํ์ ์ธ ํจํด
delete cache['key_500'];
Map์ ์ฒ์๋ถํฐ ํด์ ํ ์ด๋ธ๋ก ๊ตฌํ๋์ด ์์ต๋๋ค. ๋์ ์ถ๊ฐ/์ญ์ ๊ฐ ๋น๋ฒํ ๊ฒฝ์ฐ Map์ด ๋ ํจ์จ์ ์ ๋๋ค.
์ค์ ์ฑ๋ฅ์ ์์ง, ๋ฐ์ดํฐ ํฌ๊ธฐ, ์ ๊ทผ ํจํด์ ๋ฐ๋ผ ๋ฌ๋ผ์ง์ง๋ง, ์ผ๋ฐ์ ์ธ ๊ฒฝํฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
| ์ฐ์ฐ | Object | Map |
|---|---|---|
| ๊ณ ์ ๊ตฌ์กฐ์ ํ๋กํผํฐ ์ฝ๊ธฐ | ๋น ๋ฆ (Hidden Class ์ต์ ํ) | ๋ณดํต |
| ๋์ ํค ์ถ๊ฐ | ๋๋ ค์ง ์ ์์ (dictionary mode ์ ํ) | ๋น ๋ฆ |
| ํค ์ญ์ | ๋๋ฆผ (delete๊ฐ Hidden Class ํ๊ดด) | ๋น ๋ฆ |
| ํฌ๊ธฐ ํ์ธ | ๋๋ฆผ (Object.keys().length, O(n)) | ๋น ๋ฆ (size, O(1)) |
| ํค ์กด์ฌ ํ์ธ | 'key' in obj ๋๋ hasOwnProperty | map.has(key) (๋ ๋ค ๋น ๋ฆ) |
delete ์ฐ์ฐ์ด Object์์ ํนํ ๋น์ฉ์ด ํฐ ์ด์ ๋, Hidden Class ์ ํ(transition)์ ๋ฐ์์ํค๊ธฐ ๋๋ฌธ์
๋๋ค. V8์ ํ๋กํผํฐ๊ฐ ์ญ์ ๋๋ฉด ์๋ก์ด Hidden Class๋ฅผ ์์ฑํ๊ฑฐ๋ dictionary mode๋ก ์ ํํฉ๋๋ค. ๋ฐ๋ฉด Map์ delete()๋ ํด์ ํ
์ด๋ธ์์ ์ํธ๋ฆฌ๋ฅผ ์ ๊ฑฐํ๋ ๋จ์ํ ์ฐ์ฐ์
๋๋ค.
๋ง์ ํค-๊ฐ ์์ ์ ์ฅํ ๋, Map์ด Object๋ณด๋ค ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ ๊ฒ ์ฌ์ฉํ๋ ๊ฒฝํฅ์ด ์์ต๋๋ค. Object๋ Hidden Class ๋ฉํ๋ฐ์ดํฐ, ํ๋กํ ํ์ ์ฒด์ธ, ํ๋กํผํฐ ๋์คํฌ๋ฆฝํฐ(writable, enumerable, configurable) ๋ฑ ๋ถ๊ฐ ์ ๋ณด๋ฅผ ์ ์งํด์ผ ํฉ๋๋ค. Map์ ํค-๊ฐ ์๋ง ์ ์ฅํฉ๋๋ค.
V8 ๋ธ๋ก๊ทธ์ ๋ฐ๋ฅด๋ฉด, ์์ฒ ๊ฐ ์ด์์ ์ํธ๋ฆฌ๋ฅผ ๊ฐ์ง ์ปฌ๋ ์ ์์ Map์ ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ด Object๋ฅผ ํฌ๊ฒ ์์ญ๋๋ค.
Object์ ๊ฐ์ฅ ํฐ ์ค์ฉ์ ์ฅ์ ์ JSON๊ณผ์ 1:1 ํธํ์ ๋๋ค.
const obj = { name: 'Kim', age: 30, active: true };
const json = JSON.stringify(obj); // '{"name":"Kim","age":30,"active":true}'
const parsed = JSON.parse(json); // { name: 'Kim', age: 30, active: true }
Map์ JSON ์ง๋ ฌํ๋ฅผ ๋ค์ดํฐ๋ธ๋ก ์ง์ํ์ง ์์ต๋๋ค.
const map = new Map([['name', 'Kim'], ['age', 30]]);
JSON.stringify(map); // '{}' โ ๋น ๊ฐ์ฒด๊ฐ ๋ฉ๋๋ค
// ๋ณํ์ด ํ์ํฉ๋๋ค
JSON.stringify(Object.fromEntries(map)); // '{"name":"Kim","age":30}'
// ๋ณต์
new Map(Object.entries(JSON.parse(json)));
API ์๋ต, ์ค์ ํ์ผ, ๋ฐ์ดํฐ ๊ตํ ๋ฑ JSON์ด ํ์ํ ๊ณณ์์๋ Object๊ฐ ์์ฐ์ค๋ฝ์ต๋๋ค.
Map์ ํน์ํ ๋ณํ์ธ WeakMap์ ํค์ ๋ํ ์ฝํ ์ฐธ์กฐ๋ฅผ ์ ์งํฉ๋๋ค. ํค๋ก ์ฌ์ฉ๋ ๊ฐ์ฒด๊ฐ ๋ค๋ฅธ ๊ณณ์์ ์ฐธ์กฐ๋์ง ์์ผ๋ฉด ๊ฐ๋น์ง ์ปฌ๋ ์ ๋์์ด ๋ฉ๋๋ค.
const metadata = new WeakMap();
function processElement(element) {
// DOM ์์์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ฐ๊ฒฐ
metadata.set(element, {
clickCount: 0,
lastAccessed: Date.now(),
});
}
// element๊ฐ DOM์์ ์ ๊ฑฐ๋๊ณ ๋ค๋ฅธ ์ฐธ์กฐ๋ ์์ผ๋ฉด,
// WeakMap์ ์ํธ๋ฆฌ๋ ์๋์ผ๋ก ๊ฐ๋น์ง ์ปฌ๋ ์
๋ฉ๋๋ค.
// Map์ด์๋ค๋ฉด ๋ช
์์ ์ผ๋ก deleteํ์ง ์๋ ํ ๋ฉ๋ชจ๋ฆฌ์ ๋จ์ต๋๋ค.
WeakMap์ ์ ์ฝ์ฌํญ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
keys(), values(), entries(), forEach, size ์์๋ํ์ ์ธ ํ์ฉ ์ฌ๋ก๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
// 1. ํ๋ผ์ด๋น ๋ฐ์ดํฐ
const privates = new WeakMap();
class Counter {
constructor() {
privates.set(this, { count: 0 });
}
increment() {
const data = privates.get(this);
data.count++;
}
getCount() {
return privates.get(this).count;
}
}
// Counter ์ธ์คํด์ค๊ฐ GC๋๋ฉด private ๋ฐ์ดํฐ๋ ํจ๊ป ์ฌ๋ผ์ง๋๋ค.
// 2. ์บ์
const cache = new WeakMap();
function expensiveComputation(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = /* ๋น์ฉ์ด ํฐ ๊ณ์ฐ */ obj;
cache.set(obj, result);
return result;
}
// obj๊ฐ ๋ ์ด์ ์ฌ์ฉ๋์ง ์์ผ๋ฉด ์บ์๋ ์๋ ์ ๋ฆฌ๋ฉ๋๋ค.
Object๋ฅผ ์ฐ๋ ๊ฒฝ์ฐ:
{ name, age, email })// โ
Object๊ฐ ์ ํฉํ ๊ฒฝ์ฐ
const config = {
host: 'localhost',
port: 3000,
debug: true,
};
const { host, port } = config; // ๊ตฌ์กฐ ๋ถํด
const extended = { ...config, port: 8080 }; // ์คํ๋ ๋
Map์ ์ฐ๋ ๊ฒฝ์ฐ:
// โ
Map์ด ์ ํฉํ ๊ฒฝ์ฐ
const clickCounts = new Map();
document.querySelectorAll('button').forEach((btn) => {
btn.addEventListener('click', () => {
// DOM ์์๋ฅผ ํค๋ก ์ฌ์ฉ
const count = clickCounts.get(btn) || 0;
clickCounts.set(btn, count + 1);
});
});
// ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ ์ง์คํธ๋ฆฌ
const handlers = new Map();
handlers.set(fetchUser, { retries: 3, timeout: 5000 });
handlers.set(fetchPosts, { retries: 1, timeout: 10000 });
WeakMap์ ์ฐ๋ ๊ฒฝ์ฐ:
Object์ Map์ ๊ฒ๋ณด๊ธฐ์ ๋น์ทํ์ง๋ง ์ค๊ณ ๋ชฉ์ ์ด ๋ค๋ฆ ๋๋ค. Object๋ ํ๋กํผํฐ์ ํ์๋ฅผ ๊ฐ์ง ์ํฐํฐ๋ฅผ ํํํ๊ธฐ ์ํ ๊ฒ์ด๊ณ , Map์ ํค-๊ฐ ์์ ์ปฌ๋ ์ ์ ๋ค๋ฃจ๊ธฐ ์ํ ๊ฒ์ ๋๋ค.
์ฝ๋์์ {} ๋ฅผ ์ต๊ด์ ์ผ๋ก ์ฐ๊ณ ์๋ค๋ฉด ํ ๋ฒ ์๊ฐํด ๋ณผ ๊ฐ์น๊ฐ ์์ต๋๋ค. "์ด๊ฑด ์ํฐํฐ์ธ๊ฐ, ์ปฌ๋ ์
์ธ๊ฐ?" ์ํฐํฐ๋ผ๋ฉด Object, ์ปฌ๋ ์
์ด๋ผ๋ฉด Map์ด ๋ง๋ ๋๊ตฌ์
๋๋ค.