본문 바로가기
프로그래밍 개발/JS ES6+

Javascript ES6+ Map

by Jinseok Kim 2020. 12. 16.
반응형

 

 

Map

 

 

Map을 쓰는 이유 중 하나는 객체의 단점을 보안하기 위한 것이다.

 

 

 

 

객체의 단점

 

  • iterable하지 않다. (iterable의 의미는 순서대로 순환 및 반복한다는 흐름이다)
  • 키를 문자열로 취급한다.
  • 키값의 unique함을 완벽히 보장하지 못함.
  • 프로퍼티 개수를 직접 파악할 수 없다.

 

 

 

 

 

iterable하지 않다.

 

 

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


for (let key in o) {
  console.log(key, o[key]) 
}
/결과:
// a 1
// b 2
// c 3



Object.prototype.method = function () { }
for (let key in o) {
  console.log(key, o[key])
}
//결과:
//a 1
//b 2
//c 3
//method ƒ () { } (프로토타입의 영향으로 원하지 않는 값까지 나와버린다.)


//결국 hasOwnProperty 메소드를 이용하여 존재 key 자신의 것인지 확인해줘야한다.
for (let key in o) {
  if(o.hasOwnProperty(key)) {
    console.log(key, o[key])
  }
}
//결과:
//a 1
//b 2
//c 3


//만약 객체의 키와 값을 묶음으로 하는 배열로 만들려면 아래와 같이 여러면모로 불편하게 거쳐야한다.
const obj2Arr = obj => {
  const arr = []
  for (let key in obj) {
    if(obj.hasOwnProperty(key)) {
      arr.push([key, obj[key]]) //키와 값을 묶음
    }
  }
  return arr
}
const oArr = obj2Arr(o)
oArr.forEach(v => console.log(v))

//결과:
//(2) ["a", 1]
//(2) ["b", 2]
//(2) ["c", 3]


//하지만 map를 쓰면 iterabla 하게 간편하게 객체의 키와 값의 묶음별로 배열로 나열할 수 있다.
const oArr2 = Object.keys(o).map(k => [k, o[k]])
oArr2.forEach(v => console.log(v))

//결과:
//(2) ["a", 1]
//(2) ["b", 2]
//(2) ["c", 3]

 

 

 

 

 

 

 

키를 문자열로 취급한다.

 

 

const obj = {
  1: 10,
  2: 20,
  3: 30
}
let res = 0
for (let key in obj) {
  res += key
}
console.log(res)

//결과:
//0123 (키가 문자이다 보니 숫자 값으로가 아닌 문자로서 더했다.) 

 

 

 

 

 

 

 

키값의 unique함을 완벽히 보장하지 못함.

 

 

const obj = {
  1: 10,
  01: 20,
  '01': 30
}
console.log(obj)

//결과:
//{1: 20, 01: 30} (그냥 키 01과 콤마를 해준 '01'과 똑같은 취급을 한다.)

 

 

 

 

 

 

 

 

 

프로퍼티 개수를 직접 파악할 수 없다.

 

 

const obj = { a: 1, b: 2, c: 3 }
console.log(Object.keys(obj).length)

//결과:
//3 (keys 메소드를 사용해야한다.)

 

 

 

 

 

 

하지만 Map은 객체의 단점을 보안한다.

 

 

 

 

  1. [ key, value ] 쌍(pair)으로 이루어진 요소들의 집합. 

  2. 순서를 보장하며, iterable하다. 

  3. 키에는 어떤 데이터타입도 저장할 수 있으며, 문자열로 취급하지 않는다.

 

const map = new Map()
map.set(1, 10)
map.set(01, 20)
map.set('01', 30)
map.set({}, 40)
map.set(function(){}, ()=>{})
console.log(map)

//결과:
//Map(4) {1 => 20, "01" => 30, {…} => 40, ƒ => ƒ}

 

 

 

 

 

 

4. 추가 / 값 가져오기 / 삭제 / 초기화 / 요소의 총 개수 / 포함여부확인

 

const map = new Map()
map.set('name', 'jinseok') // set의 메소드의 첫번째 인자는 키, 두번째 인자는 값으로 들어가도록 한다.
map.set('age', 25)

console.log(map.size) // 2
console.log(map.get('name')) // jinseok
console.log(map.get('age')) // 25

map.delete('name') 
console.log(map.has('name')) // false
console.log(map.has('age')) // true
console.log(map.size) // 1

map.clear()
console.log(map.has('name')) //false
console.log(map.has('age')) // fasle
console.log(map.size) // 0

 

 

 

 

 

 

5. 초기값 지정  

 

const map1 = new Map([[10, 10], ['10', '10'], [false, true]])
console.log(map1)

const map2 = new Map(map1)
console.log(map2)
console.log(map1 === map2)

const gen = function* () {
	for (let i = 0; i < 5; i++) {
		yield [i, i+1]
  }
}
const map3 = new Map(gen())
console.log(map3)


//결과:
//Map(3) {10 => 10, "10" => "10", false => true}
//Map(3) {10 => 10, "10" => "10", false => true}
//false
//Map(5) {0 => 1, 1 => 2, 2 => 3, 3 => 4, 4 => 5}

 

 

 

 

 

 

6. 기타 메소드 소개

 

const map = new Map([[10, 10], ['10', '10'], [false, true], ['name', 'jinseok']])
const mapKeys = map.keys()
const mapValues = map.values()
const mapEntries = map.entries()

map.forEach(function(value, key, ownerMap) {
  console.log(`${key}: ${value}`)
  console.log('ownerMap: ', ownerMap, 'this: ', this)
}, [])

//결과:
//10: 10
//ownerMap:  Map(4) {10 => 10, "10" => "10", false => true, "name" => "jinseok"} this:  []
//10: 10
//ownerMap:  Map(4) {10 => 10, "10" => "10", false => true, "name" => "jinseok"} this:  []
//false: true
//ownerMap:  Map(4) {10 => 10, "10" => "10", false => true, "name" => "jinseok"} this:  []
//name: jinseok
//ownerMap:  Map(4) {10 => 10, "10" => "10", false => true, "name" => "jinseok"} this:  []

 

 

 

 

 

7. 배열로 전환하기

 

const map = new Map([[10, 10], ['10', '10'], [false, true], ['name', 'jinseok']])
const mapArray1 = [...map]
const mapArray2 = [...map.keys()]
const mapArray3 = [...map.values()]
const mapArray4 = [...map.entries()]

console.log(mapArray1, mapArray2, mapArray3, mapArray4)

//결과:
//(4) [Array(2), Array(2), Array(2), Array(2)] 
//(4) [10, "10", false, "name"] (4) [10, "10", true, "jinseok"]
//(4) [Array(2), Array(2), Array(2), Array(2)]

 

 

 

8. 객체로 전환하기

 

const map1 = new Map([[10, 10], ['10', '10'], [false, true], ['name', 'jinseok']])
const map2 = new Map([[{}, 10], [function(){}, '10'], [[], true], [Symbol('심볼'), 'jinseok']])
const convertMapToObject = map => {
  const resultObj = {}
  [...map].forEach(([k, v]) => {
    if(typeof k !== 'object') {
      resultObj[k] = v
    }
  })
  return resultObj
}
const obj1 = convertMapToObject(map1)
const obj2 = convertMapToObject(map2)

//결과:
//

 

 

 

 

 

 

WeakMap

 

 

 

  • Map에 객체를 키로 하는 데이터를 추가할 경우 Map에도 해당 객체에 대한 참조가 연결되어, 여타의 참조가 없어지더라도 Map에는 객체가 여전히 살아있다.
  • 하지만 WeakMap은 객체에 대한 참조카운트를 올리지 않아 (약한 참조), 여타의 참조가 없어질 경우 WeakMap 내의 객체는 G.C의 대상이 됨.

 

 

 

 

Map과의 비교

 

const obj1 = { a: 1 } //참조 1
const map = new Map()
map.set(obj1, 10) //참조 2
obj1 = null //null 때문에 참조 1로 변화 (아직 map의 참조는 살아있다)


const obj2 = { b: 2 } //참조 1
const wmap = new WeakMap()
wmap.set(obj2, 20)//Weakmap 때문에 여전히 참조 1
obj2 = null // 0 (결국 없어지게됨)

 

 

 

 

 

 

 

Weakmap은 참조형 데이터만 키로 설정할 수 있다.

 

const keysArray = [{a: 1}, [1, 2, 3], function(){}, Symbol('키'), 45, '문자열', false]
const wmap = new WeakMap()
keysArray.forEach((v, i) => {
  wmap.set(v, i)
  console.log(wmap.get(v))
})

//결과
//0
//1
//2
//오류 (끊김) Symbol('키')는 참조형 데이터가 아니라 기본형이기 때문이다.

 

 

 

 

 

 

Weakset과 마찬가지로 iterable하지 않다.

 

  • for ... of 사용 불가.
  • size 프로퍼티 없음
  • `keys()`, `values()`, `entries()`, `clear()` 등 사용 불가

 

 

 

 

 

 

Weakmap 활용사례

 

 

 

 비공개 객체 멤버

const weakmapValueAdder = (wmap, key, addValue) => {
  wmap.set(key, Object.assign({}, wmap.get(key), addValue))
}
const Person = (() => {  //즉시 실행 함수이므로 함수 안의 privateMembers변수 접근 불가
  const privateMembers = new WeakMap()
  return class {
    constructor(n, a) {
      privateMembers.set(this, { name: n, age: a }) 
      //weakmap을 적용했기에 자동으로 정보가 날아가게끔 하여 메모리 관리 할 수 있다.
                                                    
    }
    set name (n) {     // set get 메소드가 유일하게 Weakmap의 변수인 privateMembers에 접근가능함.
      weakmapValueAdder(privateMembers, this, { name: n })
    }
    get name () {
      return privateMembers.get(this).name
    }
    set age (a) {
      weakmapValueAdder(privateMembers, this, { age: a })
    }
    get age () {
      return privateMembers.get(this).age
    }
  }
})()
const jn = new Person('재남', 30)
console.log(jn.name, jn.age, jn)

jn.age = 25
console.log(jn.name, jn.age, jn)

const sh = new Person('서훈', 28)
console.log(sh.name, sh.age, sh)

sh.name = '성후'
console.log(sh.name, sh.age, sh)


//결과:
//재남 30 {} >(age: (...)name: (...)__proto__: Object 
//            '(...)안에 get set이 가져온 invoke 정보가 있다.')
//재남 25 {}
//서훈 28 {}
//성후 28 {}

 

 

반응형

'프로그래밍 개발 > JS ES6+' 카테고리의 다른 글

Javascript ES6+ Iterator  (0) 2020.12.18
Javascript ES6+ Iterable  (0) 2020.12.18
Javascript ES6+ Set  (0) 2020.12.16
Javascript ES6+ 심볼(Symbol)  (0) 2020.12.15
Javascript ES6+ Destructuring assignment  (0) 2020.12.14

댓글