[JS] 자료구조 - 맵과 셋, 객체 순회, 구조분해할당

PinkTopaz·2023년 5월 6일
0
post-thumbnail

맵과 셋

💡 들어가며
Map과 Set도 결국에는 객체, 그리고 이터러블 객체로 취급된다는 것을 기억하자!
또한 Map,Set은 ES6에서 새롭게 추가된 문법이다!

Map

  • 키가 있는 데이터를 저장한다는 점에서 일반 객체와 유사
  • 키에 다양한 자료형을 허용한다는 점에서 일반 객체와 차이
let map = new Map();

// ✅ 문자, 숫자, 불린형 키 모두 가능 
map.set('1', 'str1');   
map.set(1, 'num1');     
map.set(true, 'bool1'); 

// ✅ 객체는 키를 문자형으로 변환하지만 맵은 키의 타입을 변환시키지 않고 그대로 유지
alert( map.get(1)   ); 
alert( map.get('1') ); 

alert( map.size ); // 3
  • 객체와 다르게 객체 또한 키로 사용할 수 있다
let john = { name: "John" };

let visitsCountMap = new Map();
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123

💡 map[key]와 같이 사용할 수는 있지만 이 방법은 map을 일반 객체처럼 취급하기에 map을 사용할 때는 전용메서드인 get,set을 사용하자.

💡 map.set을 호출하면 set하고 난 맵 자신을 반환한다. 이에 따라 map.set을 체이닝할 수 있다.

map.set('1', 'str1')
  .set(1, 'num1')
  .set(true, 'bool1');

map을 이용한 반복작업을 도와주는 메서드

  1. map.keys() : 각 요소의 key를 모은 iterable 객체 반환
  2. map.values() : 각 요소의 value를 모은 iterable 객체 반환
  3. map.entries() : 요소의 키와 값을 을 한 쌍으로 하는 iterable 객체 반환
let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]
]);

// ✅ map.keys()
for (let vegetable of recipeMap.keys()) {
  alert(vegetable); // cucumber, tomatoes, onion
}

// ✅ map.values()
for (let amount of recipeMap.values()) {
  alert(amount); // 500, 350, 50
}

// ✅ map.entries()
for (let entry of recipeMap) { 
  alert(entry); // cucumber,500 ...
}

💡 map은 배열과 유사하게 forEach도 지원한다.

recipeMap.forEach( (value, key, map) => {
  alert(`${key}: ${value}`); // cucumber: 500 
});

객체를 map으로 바꾸기

  1. [키-값] 쌍의 배열 or 이터러블 객체 - 맵에 전달한다.
let map = new Map([
  ['1',  'str1'],
  [1,    'num1'],
  [true, 'bool1']
]);

alert( map.get('1') ); // str1
  1. 평범한 객체 - Object.entries(obj) 사용해서 객체의 키-값 쌍을 요소([key, value])로 가지는 배열을 반환받는다! (map을 이터러블 객체로 바꾸는 map.entries()와 다름!)
  • 객체를 배열로 바꾸고, map에 넣어주기 때문에 결국 1번과 같다!

  • Object.entries(obj) : 객체의 키-값([키,값]) 쌍을 요소로 가지는 배열을 반환한다.

let obj = {
  name: "John",
  age: 30
};

// ✅ [ ["name","John"], ["age", 30] ]를 map에 넣어준다!
let map = new Map(Object.entries(obj));

alert( map.get('name') ); // John

map을 객체로 바꾸기

  • Object.fromEntries : 각 요소가 [키-값]인 이터러블 객체(배열 등)을 객체로 바꿔준다!
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);

//✅ map을 이터러블 객체로 바꾸고, 그 객체를 일반 객체로 바꾼다!
// ✅ fromEntries는 이터러블 객체를 인자로 받기에 그냥 map만 넣어줘도 동일하게 동작한다!
let obj = Object.fromEntries(map.entries()); 


// 맵이 객체가 되었습니다!
// obj = { banana: 1, orange: 2, meat: 4 }

alert(obj.orange); // 2

Set

  • 중복을 허용하지 않는 특별한 컬렉션이며, key가 없는 값이 저장된다.
  • map과 다르게 요소 추가를 위해서는 add 메서드를 사용한다.
  • map과 동일하게 keys,values,entries를 이용해서 반복작업을 수행할 수 있다.

💡 Set에는 key가 없는데? keys를 어떻게 이용하지?
set.keys()와 set.values()는 동일한 작업을 한다. 둘 다 셋 내의 모든 값을 포함하는 이터러블 객체를 반환한다.
하지만 map과의 호환성을 위해 두 가지 모두 존재한다!
forEach도 map에서 value, key, map 세 가지 인수를 받을 수 있기 때문에 set에서도 value, valueAgain, set 이렇게 첫번째 인자와 동일한 두번째 인자를 받아서 세 가지 인수를 받는다!
모두 호환성을 위한 것이다!

let set = new Set();

let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };

// john, mary는 여러 번 방문
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);

// Set에는 유일무이한 값만 저장됩니다.
alert( set.size ); // 3

for (let user of set) {
  alert(user.name); // // John, Pete, Mary 
}

Object.keys, values, entries

이 세가지 메서드가 map.keys(), map.values(), map.entries()와 다른 부분은 무엇일까?

  1. obj.keys()가 아닌 Object.keys(obj)를 호출한다
  • 자바스크립트에서는 map,set 등과 같은 복잡한 자료구조들이 모두 객체를 기반으로 한다.
  • 따라서 data 객체에 자체적으로 data.keys() 라는 메서드를 구현하는 경우가 있을 수 있다. (객체 요소에는 함수도 들어갈 수 있으니까!)
  • 이렇게 커스텀 메서드를 구현한 경우에도 Object.keys(obj)를 사용하면 충돌없이 커스텀 메서드와 내장 메서드를 모두 사용할 수 있기에 Object.keys(obj)를 호출한다.
  1. Object.*를 호출하면 iterable 객체가 아닌 객체의 한 종류인 배열을 반환한다.
let user = {
  name: "John",
  age: 30
};

Object.keys(user); // ["name", "age"]
Object.values(user); // ["John", 30]
Object.entries(user); // [ ["name","John"], ["age",30] ]

객체 변환하기

객체에 배열 전용 메서드를 적용해 변환하고 싶은 경우 다음과 같은 절차를 거치면 된다.

  1. Object.entries(obj)를 사용해 객체의 키-값 쌍이 요소인 배열을 얻는다.
  2. 배열 전용 메서드를 적용한다.
  3. Object.fromEntries(array)를 적용해 배열을 다시 객체로 되돌린다.
let prices = {
 banana : 1,
 lemon : 2,
 cherry : 3,
} 

let doublePrices = Object.fromEntries(Object.entries(prices).map(([key,value])=> [key, value *2]));

alert(doublePrices.cherry); // 6

구조 분해 할당

구조 분해 할당의 활용

  • split과 같이 반환값이 배열인 메서드를 함께 사용해도 좋다.
let [firstName, surname] = "Bora Lee".split(' ');
  • 쉼표를 사용해서 요소를 무시할 수도 있다.
// ✅ 두번째 요소 무시 
let [firstName, , title] = ["Matthew", 22, "Boys Planet", "Canada"];

alert( title );
  • 배열 뿐 아니라 모든 이터러블 객체에 구조분해 할당을 할 수 있다.
let [one, two, three] = new Set([1,2,3]);
  • 객체 프로퍼티로도 할당을 할 수 있다.
let user ={};
[user.firstName, user.lastName] = "Bora Lee".split(' ');

alert(user.name); // Bora
  • 두 변수에 저장된 값을 교환할 때도 사용할 수 있다.
let guest = "Jane";
let admin = "Pete";

[guest, admin] = [admin, guest];

alert(`${guest} ${admin}`); // pete Jane

나머지 요소 가져오기 : ... 사용

let [firstName, age, ...rest] = ["Matthew", 22, "Boys Planet", "Canada"];

alert(firstName); //Matthew
alert(age); //22
alert(rest[0]);//Boys Planet

기본값 지정하기

  • 할당하고자하는 변수의 개수가 분해하려는 배열의 길이보다 작아도 에러를 일으키지 않고 undefined로 취급한다.
let [firstName, surname] = [];

alert(firstName); // undefined
alert(surname); // undefined
  • = 를 사용해 할당할 값이 없을 경우의 기본값을 지정할 수 있다.
let [name = "Guest", surname = "Anonymous"] = ["Matt"];

alert(name);    // Matt (배열에서 받아온 값)
alert(surname); // Anonymous (기본값)

// ✅ 복잡한 표현식이나 함수 호출도 기본값이 될 수 있다. 
let [name = prompt("이름을 입력하세요"), surname = prompt("성을 입력하세요")] = ["Matt"];

alert(name);    // Matt
alert(surname); // 유저가 입력한 값 

객체 분해하기

  • 구조 분해 할당으로 객체도 분해할 수 있다.
let options = {
  title: "Menu",
  width: 100,
  height: 200
};

let {title, width, height} = options;
//✅ 순서가 바뀌어도 동일하게 동작한다. 
//let {height, width, title} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200
  • 객체 프로퍼티를 key와 다른 이름으로 저장할 수 있다.
let {width: w, height: h, title} = options;

alert(w); // 100
alert(h); //200
alert(title); //Menu
  • 배열과 동일하게 초깃값도 지정할 수 있고, 함수나 표현식을 초깃값으로 지정할 수도 있다.
let options = {
	age : 22
}

let {name = "Guest", age : a, gender : g = undefined} = options;

alert(name); //Guest
alert(a); //22
alert(g); //undefined
  • ...을 이용해 나머지 패턴도 사용할 수 있지만, 일부 구식 브라우저는 지원하지 않으니 유의!
let options = {
  title: "Menu",
  width: 100,
  height: 200
};

let {title, ...rest} = options;

// ✅ rest : {  width: 100, height: 200 }
alert(rest.height); //100
alert(rest.width); //200

💡 let 없이 사용하기
지금까지는 let {title, ...rest} = {...} 이렇게 사용했는데 만약 let으로 새로운 변수를 선언하지 않고 이미 존재하는 변수에 값을 할당하고 싶다면?

let title, width, height;
//✅ 다음과 같이 쓰면 에러가 발생한다! (SyntaxError: Unexpected token '=')
{title,width,height} = { title: "Menu", width: 100, height: 200};

alert(title);

자바스크립트는 표현식(값으로 평가될 수 있는 statement [참고](https://velog.io/@dolarge/JavaScript-%ED%91%9C%ED%98%84%EC%8B%9D%EA%B3%BC-%EB%AC%B8)) 안에 있지 않으면서 주요 코드 흐름 상에 있는 {...}을 코드 블록으로 인식한다. 
따라서 `{title,width,height}`를 코드 블록으로 인식해서 에러가 발생한 것이다.
다음과 같이 할당문을 괄호로 감싸 코드 블록이 아닌 표현식으로 인식하게 하면 에러가 발생하지 않는다. 
```js
let title, width, height;
//✅ 
({title,width,height} = { title: "Menu", width: 100, height: 200});

alert(title);

중첩 구조 분해 - 예시

let options = {
  size: {
    width: 100,
    height: 200
  },
  items: ["Cake", "Donut"],
  extra: true
};

let {
  size: { // size는 여기,
    width,
    height
  },
  items: [item1, item2], // items는 여기에 할당함
  title = "Menu" // 분해하려는 객체에 title 프로퍼티가 없으므로 기본값을 사용함
} = options;

함수 매개변수를 이용해 똑똑하게 구조분해할당하기

함수의 매개변수를 선택적으로 사용하는 상황이 생겼다.

function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
}

다음과 같은 함수에서 기본값을 사용해도 되는 경우에는 함수를 이렇게 호출해야한다.

showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])

undefined를 여러개 넘겨주는 것이 지저분해보이고, 가독성도 떨어진다.

이 경우 구조분해 할당을 이용하면 좋다.

let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

//✅ 매개 변수를 객체로 모아 함수에 전달한다. 
function showMenu({title="Untitled", width:w=200, height=100, items=[]}){
    alert( `${title} ${w} ${height}` ); // My Menu 200 100
}
//✅ undefined를 명시하지 않아도 초깃값 할당이 가능하다!
showMenu(options);

💡만약 모든 변수에 초깃값을 할당하고 싶다면?
showMenu(); 이 아니라 showMenu({}); 처럼 빈괄호를 명시적으로 넘겨줘야한다.
이게 조금 귀찮다면 인수 전체의 기본값을 만들어줄 수 있다.

function showMenu({ title = "Menu", width = 100, height = 200 } = {}) {
  alert( `${title} ${width} ${height}` );
}
showMenu(); // Menu 100 200
profile
🌱Connecting the dots🌱

0개의 댓글