JS Study : ES6 - 1주차(펼침 연산자'...')

👊0👊·2020년 1월 12일
0

es6 스터디

목록 보기
2/8

순서를 갖고 있는 컬렉션 : 배열

js의 배열은 높은 유연성을 가지고 있다. 배열은 순서를 갖는 데이터 컬렉션이다.
순서를 갖기 때문에, 이를 기준으로 값을 추가하거나 제거할수 있고, 모든 위치에 값이 있는지 확인할 수 있다.

그렇지만 배열에는 수많은 메서드가 있으므로 조작(mutation)과 부수 효과(side effect)로 인한 문제가 생길 수 있다. 다행히 펼침 연산자를 사용하면 쵯고한의 코드로 배열을 빠르게 생성하고 조작할 수 있습니다.

물론 펼침연산자는 배열에 국한되지 않는다. Map, Object 등 다른 컬렉션에도 사용가능하다.

간단한 복사

펼침 연산자는 아주 간단하게 배열을 복제가능하다.

const cart = ['egg','water'];

const newCart = [...cart];

펼침 연산자의 장점기존 컬렉션을 조작하지 않는다는 점이다.

아래는 배열에서 아이템을 제거하는 함수이다. 배열을 입력받아 조건에 맞는 배열을 제거한다.
물론 기존 배열을 조작하면 부수효과가 생기기 때문에 새로운 배열을 생성한다.

function removeItem(itmes, removalbe) {
  const updated = [];
  for (let i = 0; i < items.length; i++) {
    if (items[i] !== removalbe) {
      updated.push(items[i]);
    }
  }
  return updated;
}

나쁜 코드는 아니지만, 읽기 나쁘고 반복문과 조건문이 많아서 복잡하다.
한번 splice 메서드로 리팩토링 해보자

function removeItem(itmes, removalbe) {
  const index = items.indexOf(removalbe);
  items.splice(index, 1);
  return items;
}

코드는 간단해졌지만, 기존 배열을 조작(mutation)해서 부수효과(side effect)가 발생했다.
하지만 splice 대신, slice를 사용하면 기존 배열을 조작하지 않는 함수를 만들 수 있다.

function removeItem(itmes, removalbe) {
  const index = items.indexOf(removalbe);
  return items.slice(0, index).concat(items.slice(index + 1));
}

순수한 함수로 리팩토링되었고 코드도 짧아졌다. 하지만 가독성이 좀 나쁘다.
대망의 펼침 연산자가 들장할 차례이다.

function removeItem(itmes, removalbe) {
  const index = items.indexOf(removalbe);
  return [...items.slice(0, index), ...items.slice(index + 1)];
}

읽기 쉽고, 재사용 가능하며 예측가능한 함수로 리팩토링되었다.

😅😅 사실, 제일 좋은 방법은 filter()를 이용하는 것이라 생각한다.
const newItem = items.filter(item => item !== removable);
or
const removeItem = (items, removable) => items.filter(item => item !== removable);

push, unshift 메서드를 대체, sort 보안

펼침 연산자는 push(), unshift 메서드를 대체할 수도 있다.
아래 코드는 길이가 2인 배열에 새로운 객체를 추가하는 함수이다.

push

function addItem(items) {
  if (items.length > 2) {
    items.push(item);
    return items;
  }
  return items;
}

간단한 함수지만 push 메서드는 기존 배열을 조작한다. 펼침 연산자로 새로운 배열을 만들어보자.

function pushItem(items) {
  if (items.length > 2) {
    return [...items, item];
  }
  return items;
}
function pushItem(items) {
  return items.length > 2 ?
    [...items, item] : items;
}

unshift

push와는 반대로 생각해보자.

function unshiftItem(items) {
  return items.length > 2 ?
    [item, ...items] : items;
}

sort

sort또한 원본을 조작한다. 그럼 펼침연산자로 간단하게 복제해서 사용해보자.

const sortedItems = [...items].sort();

객체 : 데이터를 쉽게 갱신해보자.

es6에서 펼침 연산자가 큰 인기를 얻었으므로 유사한 문법을 객체에 사용하는 방안도 제안되었다. 객체 펼침 연산자는 많은 사람이 사용하면서 ES2018 명세에 공식적으로 포함되었다.

우리는 객체를 병합이나 복사할 때 Object.assign()을 사용하곤 했다. assign()은 충분히 훌룡하지만, 병합이나 복사같은 단순한 표현임에도 가독성을 나쁘게한다.

아래코드는 기존 객체의 복사와 병합의 코드를 객체 펼침 연산자를 사용하여 리팩토링한 코드이다.

아래는 객체 펼침 연산자를 사용한 예시이다.

const draft = {
  title : "펼침 연산자",
  author : "권영권"
}

const post = {
  content : "너무 좋은 글"
}

// 복사
const copyDraft1 = Object.assign({}, draft);
const copyDraft2 = {...draft};
// 병합
const mergeObj1 = Object.assign({}, draft, post);
const mergeObj2 = {...draft, ...post};
// 병합2                         
const update = { ...post, createdBy : 20200112 };
// { title : "펼침 연산자", author : "권영권", createdBy : 20200112 }

깊은 복사할 때도 좀 더 편하다

const employee = Object.assign(
  {},
  defaultEmployee,
  {
    name: Object.assign({}, defaultEmployee.name),
  },
);


const employee2 = {
  ...defaultEmployee,
  name: {
    ...defaultEmployee.name,
  },
};

아까 배열에서 사용한 방식을 그대로 사용할 수 있게되었다. 코드가 좀 더 읽기 편해지고, 의도도 명확해졌다.

맵 : 키-값 데이터를 순회하기

객체는 정적인 키-값 데이터를 다룰 때 효과적이다. 하지만 어느 부분에서는 ES6에 추가된 Map이 좀 더 효과적일 수 있다.

  • 키-값 쌍이 자주 추가되거나 삭제되는 경우
  • 키가 문자열이 아닌경우

아래 코드는 데이터에 값을 추가하고 삭제하고 모두 지우는 함수이다.

const dogs = [
  { name: 'max', size: l },
  { name: 'dony', size: xl },
  { name: 'shadow', size: sm }
  ]

function addFilters(filters, key, value) {
  filters[key] = value; // 1) 객체 자체의 메소드=
}

function deleteFilters(filters, key) {
  delete filters[key]; // 2) delete 연산자
}

function clearFilters(filters) {
  filters = {}; // 3) 변수 재할당 
  return filters;
}

키-값 설정, 삭제, 전체 제거처럼 단지 세 가지 기본적인 동작을 수행하는 데도 불구하고 서로 다른 세 가지 패러다임을 적용했다.

하지만 Map을 사용하면 좀 더 명확해진다. 맵은 객체와 다르게 키-값 쌍을 자주 변경하는 경우에 적합하도록 설계되어있기 떄문에, 성능상의 이점도 있다.

function addFilters(filters, key, value) {
  filters.set(key, value);
}

function deleteFilters(filters, key) {
  filters.delete(key);
}

function clearFilters(filters) {
  filters.clear();
}

이번 주제는 맵에 대한 주제가 아니니, 맵보다는 펼침 연산자로 다시 이야기를 되돌아가자.

객체는 순회하기가 매우 번거롭다. 실제로 객체를 직접 순화할 방법은 없었다.for...in문은 객체 키 이외에는 접근할 수 없다.

아래 코드는 키-값 쌍을 '키:값'형식의 문자열로 변환하는 코드이다.
객체는 순서를 보장하지 않기 때문에 필터링 조건을 변경하려면 먼저 키를 정렬해야 한다.

const filters = {
  color: 'black',
  breed: 'labrador',
};

function getSortedAppliedFilters(filters) {
  const keys = Object.keys(filters);
  keys.sort();
  const applied = [];
  for (const key of keys) {
    applied.push(`${key}:${filters[key]}`);
  }
  return `선택한 조건은 ${applied.join(', ')} 입니다.`;
}
// '선택한 조건은 견종:래브라도레트리버, 색상:검정색 입니다.

간단한 순회를 위해 관리해야 할 것이 너무 많다. 반면 맵은 정렬과 순회에 필요한 기능이 내장되어 있다.

const filters = new Map()
  .set('색상', '검정색')
  .set('견종', '래브라도레트리버');

function checkFilters(filters) {
  for (const entry of filters) {
    console.log(entry);
  }
}
// ['색상', '검정색']
// ['견종', '래브라도레트리버']

filters.entries();

훨신 간단해졌다. 맵은 순서를 저장하기 때문에, 따로 정렬을 하지않아도 된다.
다만 정렬이 필요한 경우 sort() 메서드가 내장되어 있지 않기 때문에 추가 작업이 필요하다.

맵 펼치기

맵에도 배열과 동일하게 펼침 연산자를 사용할 수 있다. 맵 객체의 경우에는 키-값 쌍이 반환된다.
그렇다면 키-값 쌍의 배열을 쉽게 만들수 있다.

[...filters];
// [['색상', '검정색'], ['견종', '래브리도레트리버']]

그럼 코드는 더 간단해질 수 있다.
아래 코드는 펼침연사자와 sort() 메서드를 사용한 방법이고
두 번째는 체이닝을 사용한 방법이다.

function sortByKey(a, b) {
  return a[0] > b[0] ? 1 : -1;
}

function getSortedAppliedFilters(filters) {
  const applied = [];
  for (const [key, value] of [...filters].sort(sortByKey)) {
    applied.push(`${key}:${value}`);
  }
  return `선택한 조건은 ${applied.join(', ')} 입니다.`;
}


function getSortedAppliedFilters(filters) {
  const applied = [...filters]
    .sort(sortByKey)
    .map(([key, value]) => {
      return `${key}:${value}`;
    })
    .join(', ');

  return `선택한 조건은 ${applied} 입니다.`;
}

부수효과 피하기

우리는 배열과 객체에서 조작을 피해서 부수효과를 피해왔다.
맵에서도 비슷하게 부수 효과를 피할 수 있다.

만약 기본값이 있고, 사용자의 조건을 기본값에 덮어쓰이는 로직을 짜려고 한다.
어떻게 해야할까?

const defaults = new Map()
  .set('색상', '갈색')
  .set('견종', '비글')
  .set('지역', '캔자스');

const filters = new Map()
  .set('색상', '검정색');

function applyDefaults(map, defaults) {
  for (const [key, value] of defaults) {
    if (!map.has(key)) {
      map.set(key, value);
    }
  }
}

위의 코드는 filters를 조작해서 부수효과를 만든다. 배열과 객체에서 사용한 방법을 이용해보자.

새로운 맵을 생성해보자.

function applyDefaults(map, defaults) {
  const copy = new Map([...map]);
  for (const [key, value] of defaults) {
    if (!copy.has(key)) {
      copy.set(key, value);
    }
  }
  return copy;
}

이제 부수효과로 안전한 코드가 작성됬다. 하지만 너무 조건문과 반복문으로 코드를 이해하기 복잡하다. 펼침 연산자를 좀 더 적극적으로 사용해보자.

function applyDefaults(map, defaults) {
  return new Map([...defaults, ...map]);
}

똑같은 로직을 갖고 있는 함수이지만, 훨씬 읽기 편해졌다.

정리

배열, 객체, 맵에서 펼침 연산자가 왜 유용한지를 알아봤다.
부수효과를 줄이기 위해서, 데이터를 조작할 때 새로운 컬렉션을 생성한다.
기존 방법으로는 가독성을 해치기 때문에 펼침 연산자가 추가되었다.
처음에는 배열에만 사용하였지만, 이젠 객체, 맵에서도 사용이 가능하다.

Bye~~

참고 자료

profile
ㅎㅎ

0개의 댓글