hoon's bLog

Javascript 자바스크립트 코딩의 기술 5장 반복문을 단순하게 만들어라 (2) 본문

IT/Javascript

Javascript 자바스크립트 코딩의 기술 5장 반복문을 단순하게 만들어라 (2)

개발한기발자 2024. 2. 6. 16:17
반응형


5장 배열로 데이터 컬렉션을 관리하라

tip23. filter( )와 find( )로 데이터의 부분집합을 생성하라

다음은 문자열이 담긴 간단한 배열에 필터링을 적용하는 예제 코드이다.

const team = ['Michelle B', 'Dave L', 'Dave C', 'Courtne B', 'Davina M'];

'Dave'.match(/Dav/);
// ["Dav", index: 0, input: "Dave", groups: undefined]
'Michelle'.match(/Dav/);
// null
  • match() : 문자열이 정규 표현식과 일치하면 일치한 항목에 대한 정보를 배열로 반환, 일치하지 않으면 null 반환!!
  • filter() 메서드로 수정해보면 다음과 같다.
const daves = team.filter((member) => member.match(/Dav/));
// ['Dave L', 'Dave C', 'Davina M']
  • match() 메서드로 team 배열에서 'Dav' 항목이 포함된 항목을 filter() 메서드로 배열로 반환
  • 다음은 근무중인 '기념 도서관' 사서를 찾는 예제 코드를 보자
const instructors = [{
     name: '짐',
     libraries: ['미디어교육정보 도서관']
},{
     name: '새라',
     libraries: ['기념 도서관', '문헌정보학 도서관']
},{
     name: '엘리엇',
     libraries: ['중앙 도서관']
}]

let memorialInstructor;

for (let i = 0; i < instructors.length; i++) {
     if (instructors[i].libraries.includes('기념 도서관')) {
          memorialInstructor = instructors[i];
          break;
     }
})
  • 두번째 사서가 일치하여, 조건에 따라 두번째 루프에서 break
  • for문과 if문 대신 find() 메서드로 수정해보자!
const librarian = instructors.find((instructor) => {
    return instructor.libraries.includes('기념 도서관');
});
// { name: '새라', libraries: ['기념 도서관', '문헌정보학 도서관']}
  • find() : 일치하는 값 return, 없을경우 undefined 반환!
  • '기념 도서관'을 포함한 instructor 객체의 libraries 프로퍼티 return
  • 추후 커링(currying, 다중 인수을 갖는 함수를 단일 인수를 갖는 함수들의 함수열로 바꾸는 것)을 통해 코드를 좀 더 개선해 볼 예정

tip24. forEach( )로 동일한 동작을 적용하라

  • map(), filter(), find()와 달리 forEach()는 배열을 전혀 변경하지 않는다.
  •  예측 가능하면서도 다른 배열 메서드와 같이 작동하여 함께 연결할 수 있기 때문이다.
  • forEach()는 함수의 유효 범위를 벗어나는 작업이 필요한 경우에 가장 좋다.
  • 반드시 Side Effect가 발생한다는 점을 주의해야 한다.
  • 그럼에도 불구하고 사용하는 이유는 체이닝 과정에서 다른 배열 메소드와 결합할 수 있기 때문이다.
  • 회원들에게 이메일로 초대장을 보내는 예제 코드로 알아보자!
const sailingClub = ['Lee', 'Andy', 'Kim', 'Mike', 'Song'];

for (let i = 0; i < sailingClub.length; i++) {
    sendEmail(salingClub[i]);
}
  • sendEmail 함수를 sailingClub의 길이만큼 호출하는 코드
  • forEach를 사용하는 예제를 만들어보자
sailingClub.forEach((member) => sendEmail(member));
  • 위 코드는 이메일을 보내는 것은 부수 효과(사이드 이펙트)지만, 데이터를 조작하지 않는다.
  • 이럴때 forEach()를 사용하면 약간의 예측 가능성을 얻을 수 있다.

tip25. 체이닝으로 메서드를 연결하라

체이닝이란, 값을 다시 할당하지 않고 반환된 객체에 메서드를 즉시 호출하는 것을 의미한다.
앞에 살펴봤던 회원들에게 메일 보내는 예제를 통해 알아보자.

const sailors = [
  {
    name: 'yi hong',
    active: true,
    email: 'yh@hproductions.io',
  },
  {
    name: 'alex',
    active: true,
    email: '',
  },
  {
    name: 'nate',
    active: false,
    email: '',
  },
];

const active = sailors.filter((sailor) => sailor.active);

const emails = active.map(
  (member) => member.email || `${member.name}@wiscsail.io`
);

emails.forEach((sailor) => sendEmail(sailor));
  • line 19 : active: true 인 사람으로 추리기
  • line 21 : email을 가지고 있으면 해당 email 사용, 그렇지 않으면 '이름@wiscsail.io' 형태의 email 사용
  • line 25 : line 21의 emails 배열에 있는 sailor를 sendEmail 함수로 메일 발송!
  • line 19부터 이를 체이닝을 사용하여 코드를 변경하면,
sailors.filter((sailor) => sailor.active)
    .map((sailor) => sailor.email || `${sailor.name}@wiscsail.io`)
    .forEach((sailor) => sendEmail(sailor));
  • 이렇게 체이닝을 사용하면 변수에 할당하는 과정이 생략된다!
  • forEach() 메서드는 배열을 반환하지 않으므로 가장 마지막에 사용해야 한다.
  • 언뜻 좋아보이지만 단점은 새로운 메서드를 호출할 때 마다 반환된 배열 전체를 다시 반복한다.
  • tip24에서의 for문은 sailors의 길이만큼 3번만 반복하면 될 코드를, 체이닝으로 메서드를 연결하면 filter에서 3번, map에서 2번, forEach에서 2번 총 7번을 반복한다.
  • 보기 코드와 같이 데이터가 그리 많지 않은 경우는 for문을 쓰는게 좀 더 이득으로 보인다. 하지만 대용량 데이터를 하면 위와 같이 체이닝으로 리팩토링하는 것도 고려해야 하겠다.

tip26. reduce( )로 배열 데이터를 변환하라

  • reduce() : 메서드는 원본 배열과는 크기(길이)와 형태가 다른 새로운 배열을 생성
  • 배열을 이용해 다른 새로운 자료구조 데이터를 만들어야 할때 사용한다.
const callback = function(collectedValues, item) {
    return [...collectedValues, item]; // 누적된 값을 반환하는 반환값
};

const saying = ['veni', 'vedi', 'veci'];

const initialValue = [];

const copy = saying.reduce(callback, initialValue); // 콜백함수, 기본값을 받는다.

copy; // ['veni', 'vedi', 'veci'];
  • 콜백함수의 return [...collectedValues, item] 이 부분을 계속 누적하면서 반환한다.
    • 1번째 순회 때는 collectedValues의 값은 initialValue인 []이다. 따라서 첫 순회때 return 값은 [...[], 'veni'] = ['veni']
    • 2번째 순회 때는 collectedValues의 값은 [...[], 'veni'] 이므로 return 값은 [...[...[], 'veni'], 'vedi'] = ['veni', 'vedi']
    • 3번째 역시 2번째랑 같은 원리로 [...[...[...[], 'veni'], 'vedi'], 'veci'] = ['veni', 'vedi', 'veci']
    • 결국 마지막 반환값은 ['veni', 'vedi', 'veci']이다. 
  • 다음은 고윳값을 분류하는 코드를 보자.
const dogs = [
  {
    이름: '맥스',
    크기: '소형견',
    견종: '보스턴테리어',
    색상: '검정색',
  },
  {
    이름: '도니',
    크기: '대형견',
    견종: '래브라도레트리버',
    색상: '검정색',
  },
  {
    이름: '섀도',
    크기: '중형견',
    견종: '래브라도레트리버',
    색상: '갈색',
  },
];

const colors = dogs.reduce((colors, dog) => {
    if (colors.includes(dog['색상'])) {
        return colors;
    }
    return [...colors, dog['색상']];
}, []);

console.log(colors); //  ["검정색", "갈색"]
  • line 26 :  누적값을 반환하는 코드가 없으면 에러가 발생한다.
  • line 27 : [] 정의를 내리지 않았으면 dogs의 첫번째 요소를 사용한다.
  • 첫번째 순회 때, colors는 [], dog = {이름: '맥스', 크기: '소형견', 견종: '보스턴테리어', 색상: '검정색'}로 line 26의 return값은 ['검정색']
  • 첫번째 return 값은 ['검정색']이 두번째 순회 때, colors의 인수가 되어 colors=['검정색'], dog은 dogs의 두번째 요소인 {이름: '도니', 크기: '대형건', 견종: '래브라도레트리버', 색상: '검정색'} 가 된다.
  • 때문에 colors의 색깔중에 dog['색상']인 '검정색'을 포함하고 있기 때문에, 그냥 color('검정색')를 return 하고 세번째 순회의 colors의 인수로 넘긴다.
  • reduce()를 이용해 데이터의 일부를 반환해 크기(길이)를 변경했고, 형태도 변경했다.

tip27. for...in 문과 for...of 문으로 반복문을 정리하라

  • 필요한 결과와 일치하지 않을 때와 자료구조가 배열이 아닌 경우에는 배열메서드를 사용할 필요 없을 수도 있다.
  • 아래 코드는 사용자가 회사를 선택하면 정보를 추가, 삭제하는 작업이므로 맵을 사용하면 쉽게 처리할 수 있다. 이 코드를 통해 for...in, for...of를 살펴보자.
const firms = new Map()
  .set(10, 'Zoom')
  .set(23, 'Apple')
  .set(33, 'Google');

// Map(3) {10 => "Zoom", 23 => "Apple", 33 => "Google"}
  • isAvailable() 함수는 이용할 수 있는 회사인지 체크하는 코드인데 그냥 호출용 함수코드
  • 이제 선택한 회사들 모두 서비스를 사용할 수 있는지, 없는지 확인해보는 코드를 살펴보자.
function checkConflicts(firms, isAvailable) {
  // START:loop
  const entries = [...firms];
  for (let i = 0; i < entries.length; i++) {
    const [id, name] = entries[i];
    if (!isAvailable(id)) {
      return `${name}는 사용할 수 없습니다`;
    }
  }
  return '모든 회사를 사용할 수 있습니다';
  // END:loop
}

function checkConflicts(firms, isAvailable) {
  // START:reduce
  const message = [...firms].reduce((availability, firm) => {
    const [id, name] = firm;
    if (!isAvailable(id)) {
      return `${name}는 사용할 수 없습니다`;
    }
    return availability;
  }, '모든 회사를 사용할 수 있습니다');
  return message;
  // END:reduce
}
  • 위 코드를 for...infor...of를 사용하면 다음과 같다.
//for...in 사용
function checkConflictsForIn(firms, isAvailable) {
  // START:for
  for (const id in firms) {
    if (!isAvailable(parseInt(id, 10))) {
      return `${firms[id]}는 사용할 수 없습니다`;
    }
  }
  return '모든 회사를 사용할 수 있습니다';
  // END:for
}


//for...of 사용
function checkConflictsForOf(firms, isAvailable) {

  for (const firm of firms) {
    const [id, name] = firm;
    if (!isAvailable(id)) {
      return `${name}는 사용할 수 없습니다`;
    }
  }
  return '모든 회사를 사용할 수 있습니다';

}

결론

배열 메서드이든, forEach든, for iterator 문이든 결국엔 적재적소에 사용하는 것이 중요하다!

메서드의 기능을 까먹지 않도록 계속해서 사용하고 연습해야만, 내 것이 된다!

고로 계속 연습하고 공부하자!

 

Reference

자바스크립트 코딩의 기술 요약 / 정리

728x90
반응형