자바스크립트 코딩의 기술_배열로 데이터 관리하기

자바스크립트 2021. 5. 22. 15:10

(본 포스팅은 길벗의 '자바스크립트-코딩의 기술' 책을 공부하면서 작성되었습니다_내돈내산)

 

1. 배열로 유연한 컬렉션을 생성하라

  • 원래 자바스크립트에는 데이터 컬렉션을 다루는 구조로 배열과 객체 두가지만 있었다. 하지만 모던 자바스크립트로 넘어오면서 맵(Map), 세트(Set), 위크 맵(WeakMap), 위크셋(WeakSet) 이 추가되었다.
  • 어떤 형태로든 조작(추가, 제거, 정렬, 필터링, 교체 등) 해야한다면 배열이 가장 적합한 컬렉션이 될것임.
  • 객체를 순회하려면 먼저 Object.Keys() 를 실행해서 객체의 키를 배열에 담은 후 생성한 배열을 이용해 배열을 객체와 반복문의 가교로 활용하여 순회한다.(아래 예시)
function getTotalStats() {
// # START:loop

  const game1 = {
    player: 'Jim Jonas',
    hits: 2,
    runs: 1,
    errors: 0,
  };

  const game2 = {
    player: 'Jim Jonas',
    hits: 3,
    runs: 0,
    errors: 1,
  };

  const total = {};

  const stats = Object.keys(game1);			// *****
  for (let i = 0; i < stats.length; i++) {		// *****
    const stat = stats[i];				// *****
    if (stat !== 'player') {				// *****
      total[stat] = game1[stat] + game2[stat];		// *****
    }							// *****
  }							// *****

  // {
  //   hits: 5,
  //   runs: 1,
  //   errors: 1
  // }

  // # END:loop
  return total;
}
  • 배열에는 iterable이 내장되어 있어 컬렉션의 항목을 한번에 하나씩 처리하는 것이 가능함. 그래서 다양한 분야에 많이 쓰고 있음.

2. Include( ) 로 존재 여부를 확인하라

  • include() 메서드를 통해 배열에 있는 값의 위치를 확인하지 않고 존재여부를 확인할 수 있음.
  • 존재여부를 확인하는 것은 삼항연산자와 단락평가를 비롯해 대부분의 조건문에서 중요하게 작용한다.
  • 자바스크립트에서 배열이 특정문자열을 포함하고 있는지 확인하려면 문자의 위치를 찾아내야 하는데, 특정문자열이 존재하면 해당 문자열의 index 값을 이용해 위치를 확인할 수 있다. 하지만 문제는 index값이 0 이 되어버리면 자바스크립트에서는 이를 false로 평가해버린다. 이때문에 존재하는 값이어도 false를 반환할 수 있는 문제가 있다. 다음과같은 예시가 그렇다.
const sections = ['shipping'];

function displayShipping(sections) {
  if (sections.indexOf('shipping')) {
    return true;
  }
  return false;
}
  • 이와같은 상황을 해결하기 위해 -1 값을 이용한 조건문을 구현해야 하는 번거로움이 있었음.
const sections = ['contact', 'shipping'];

function displayShipping(sections) {
  return sections.indexOf('shipping') > -1;
}
  • 이러한 번거로움을 해결하기 위해 ES2016에서 추가된 새로운 기능인 include( )를 이용해서 존재여부에 따라 boolean값으로 true 또는 false를 반환하도록 한다. (색인이 0인경우 false를 반환하는 문제 해결)
const sections = ['contact', 'shipping'];

function displayShipping(sections) {
  return sections.includes('shipping');
}

3. 펼침연산자(spread, ....) 로 배열을 본떠라

  • 펼침연산자는 마침표 세개(...)로 표시하며, 배열에 포함된 항목을 목록으로 바꿔주는 역할을 한다.
  • 맵, 매개변수, 제너레이터 등 다양한 부분에 사용이 가능함.
const cart = ['Naming and Necessity', 'Alice in Wonderland']
...cart // 이렇게 사용할 경우 오류가 발생함. 펼침연산자를 단독으로 사용할 수 없음.
const copyCart = [...cart]; // 이런식으로 정보를 어디에든 펼쳐 넣어야 한다.
  • 다음 예시를 통해 펼침연산자가 어떤식으로 쓰일 수 있는지 알아볼 수 있다.
// 반복문 이용
function removeItem(items, removable) {
  const updated = [];
  for (let i = 0; i < items.length; i++) {
    if (items[i] !== removable) {
      updated.push(items[i]);
    }
  }
  return updated;
}
// 배열메서드인 splice( ) 사용
function removeItem(items, removable) {
  if (items.includes(removable)) {
    const index = items.indexOf(removable);
    items.splice(index, 1);
  }
  return items;
}

const books = ['practical vim', 'moby dick', 'the dark tower'];
const recent = removeItem(books, 'moby dick');
const novels = removeItem(books, 'practical vim');
  • 첫번째는 배열에서 항목을 제거하기 위해 반복문을 사용한 예시이다. 하지만 코드가 복잡하고 어수선하기 때문에 배열메서드인 splice()를 사용해서 리팩토링을 거쳐 두번째 코드가 탄생했다.
  • 하지만 첫번째 removeItem( )에 books라는 배열을 전달하여 'moby dick'을 제거한 배열을 반환받는과정에서 원본배열인 books도 함께 변경되었다.
  • 함수에 전달하는 정보가 근본적으로 달라지는것이 매우 안좋은 현상이 발생한다.
// 배열메서드인 slice() 사용
function removeItem(items, removable) {
  if (items.includes(removable)) {
    const index = items.indexOf(removable);
    return items.slice(0, index).concat(items.slice(index + 1));
  }
  return items;
}
  • 위의 현상을 개선하기 위해 원본배열을 변경하지 않고 배열의 일부를 반환하는 배열메서드인 slice()를 사용하였다. 
  • 하지만 코드의 가독성이 떨어지고, concat이 배열 두개를 병합한다는 의미를 알고 있어야 코드를 이해할 수 있다.
// 펼침연산자(spread)를 이용한 코드 구현
function removeItem(items, removable) {
  if (items.includes(removable)) {
    const index = items.indexOf(removable);
    return [...items.slice(0, index), ...items.slice(index + 1)];
  }
  return items;
}
  • 펼침 연산자를 이용해 코드를 구현하였을때, 일단 데이터의 조작이 일어나지 않았고, 읽기 쉽고 간편해졌다. 재사용할수 있으며, 직관적으로 코드를 이해할 수 있게 변했다.
//  # START:spreadArguments
const book = ['Reasons and Persons', 'Derek Parfit', 19.99];

function formatBook(title, author, price) {
  return `${title} by ${author} $${price}`;
}
//  # END:spreadArguments


//  # START:spreadArgumentsExample
formatBook(book[0], book[1], book[2]);

//  # END:spreadArgumentsExample

//  # START:spreadArgumentsExample2
formatBook(...book);
//  # END:spreadArgumentsExample2
  • 위에서 볼 수 있듯이 formatBook처럼 목록을 인수로 받아 반환하는 함수가 있다. 그리고 그 목록이 순서대로 들어간 배열값이 존재한다고 할때. 펼침연산자를 이용해 그 배열을 목록화 시키는 기능을 이용할 수 있다.

4. push( ) 메서드 대신 펼침연산자로 원본 변경을 피하라

  • 코드에서 원본데이터의 조작이 발생한 경우 예상치 못한 결과를 낳을 수 있는 가능성이 증가한다. 
  • 모던 자바스크립트의 상당수가 함수형 프로그래밍 형식을 취하기 때문에 기타 효과와 조작이 없는 코드를 작성해야 한다.
  • 우리가 가장 흔하게 사용하는 배열메서드 중 push( )라는 메서드가 있다. 이는 새로운 항목을 배열뒤에 추가해 원본배열을 변경하는 메서드이다.
  • 이는 순수함수(pure function)을 만드는데 있어서 문제가 될 수 있다.(순수함수: 함수에 부수적인 효과가 없는 것)
//  # START:titles
  const titles = ['Moby Dick', 'White Teeth'];
  const moreTitles = [...titles, 'The Conscious Mind']; // title.push('The Conscious Mind')
// ['Moby Dick', 'White Teeth', 'The Conscious Mind'];
//  # END:titles
}

function forgetting() {
//  # START:forgetting
// Add to beginning.
  const titles = ['Moby Dick', 'White Teeth'];
  titles.shift('The Conscious Mind'); // 원본배열 변경, slice의 기능을 명확히 알아야 이해 가능

  const moreTitles = ['Moby Dick', 'White Teeth'];
  const evenMoreTitles = ['The Conscious Mind', ...moreTitles]; // 새로운 배열로 원본배열 조작없음.

  // Copy
  const toCopy = ['Moby Dick', 'White Teeth'];
  const copied = toCopy.slice();

  const moreCopies = ['Moby Dick', 'White Teeth'];
  const moreCopied = [...moreCopies];
//  # END:forgetting

5. 펼침연산자로 정렬에 의한 혼란을 피하라

  • 배열을 여러번 정렬해도 항상 같은 결과가 나올 수 있도록 펼침연산자를 사용할 수 있다.
  • 펼침연산자로 여러가지 조작함수를 대체할 수 있다고 설명했다. 하지만 대체 불가능한 함수가 있을 때는 어떤 방법을 사용하는 것이 좋을까. 답은 펼침연산자로 원본 배열의 사본을 생성하고, 사본을 조작하면 된다.
  • 자바스크립트의 정렬함수 중 sort( )라는 함수가 있다. 이 함수는 원본배열의 순서를 변경해서 원본을 참조하는 값을 반환하게 된다. 
// (1) 원본배열
const staff = [
  {
    name: 'Joe',
    years: 10,
  },
  {
    name: 'Theo',
    years: 5,
  },
  {
    name: 'Dyan',
    years: 10,
  },
];

// (2) years를 오름차순으로 정렬한 배열
// [
//   {
//     name: 'Theo',
//     years: 5
//   },
//   {
//     name: 'Joe',
//     years: 10
//   },
//   {
//     name: 'Dyan',
//     years: 10
//   },
// ];

// (3) (2)결과를 이름순으로 정렬한 배열
// [
//   {
//     name: 'Dyan',
//     years: 10
//   },
//   {
//     name: 'Joe',
//     years: 10
//   },
//   {
//     name: 'Theo',
//     years: 5
//   },
// ];

// (4) (3)결과를 다시 years 오름차순으로 정렬한경우
// [
//   {
//     name: 'Theo',
//     years: 5
//   },
//   {
//     name: 'Dyan',
//     years: 10
//   },
//   {
//     name: 'Joe',
//     years: 10
//   },
// ]
  • 위의 결과를 보았을때, (2)번결과와 (4)번결과 모두 연도를 오름차순으로 배열한 결과인데 배열의 순서가 다르게 나오는것을 알 수 있다. 이는 sort를 이용해 원본배열이 변경되었기 때문이다. 정렬결과가 매번 바뀌게 된다면 그 결과에 대한 신뢰도가 하락하기 때문에 좋은 경우가 아니게 된다.
  • 아래와같이 펼침연산자를 이용해 배열의 사본을 만들어 사본을 조작하면 위와같은 결과를 방지할 수 있다.
[...staff].sort(sortByYears);

// [
//   {
//     name: 'Theo',
//     years: 5
//   },
//   {
//     name: 'Joe',
//     years: 10
//   },
//   {
//     name: 'Dyan',
//     years: 10
//   },
// ];
admin