혼자 적어보는 노트

프로그래머스 데브코스 TIL - Day 8 본문

스터디

프로그래머스 데브코스 TIL - Day 8

jinist 2022. 3. 30. 23:50

 학습 목차

- [Day 8] JavaScript 주요 문법 (8)

- 함수형 프로그래밍과 ES6+ (2)

 

✅ 새롭게 학습한 부분

- DOM

- Virtual DOM

- 함수형 프로그래밍 장바구니 예제

- 제너레이터:이터레이터 프로토콜의 지연 평가

 

DOM(Document Object Model)

웹페이지의 구성 요소를 제어하기 위해 문서의 구성요소들을 객체로 구조화하여 나타낸 모델

 

 

viortual DOM은 어떤 문제를 해결하기 위해 등장 했을까?

- DOM에 변화가 가해진다면 렌더트리를 기준으로 레이아웃을 배치하고 그리는 작업을 다시 하게된다.
- 즉, DOM을 반복적으로 조작했을 때 렌더링을 자주 하게되며 시간을 소모하게 된다.

 

Virtual DOM

- DOM을 추상화시킨 자바스크립트 객체를 의미한다.
- 직접 돔을 수정하지 않고 업데이트 된 부분을 모아서 실제 DOM과 변경된 Virtual DOm의 차이를 비교한 후
  변경된 부분만 실제 DOM에 반영하는 방식.

 

Virtual DOM이 무조건 빠를까?

- DOM의 가상의 복사본인 virtualDOM을 생성하는 것 또한 메모리를 차지하는 부분이기 때문에

  메모리 사용이 늘어난다.
- 동시에 변경되는 것에 한해서만 렌더링이 1번 진행되기 때문에 반복적인 움직임이 있을 경우
  똑같이 최적화를 해주어야 한다.

 

+ createDocumentFragment

DocumentFragment는 기본적으로 DOM과 동일하게 동작하지만,
HTML의 DOM 트리에는 영향을 주지 않으며, 메모리에서만 정의된다.
다른 노드를 담는 임시 컨테이너와 같은 역할을 하는 노드

 


go, pipe, map, reduce를 사용하여

총 수량과 총 가격 구하기

(* map,reduce에는 curry가 감싸져있다.)

const go = (...args) => reduce((a, f) => f(a), args);
const pipe = (...fs) => (a) =>go(a, ...fs);

const fruits = [
  { name: "사과", price: 1000, quantity: 1 },
  { name: "오렌지", price: 2000, quantity: 2 },
  { name: "포도", price: 3000, quantity: 3 },
  { name: "파인애플", price: 5000, quantity: 4 },
  { name: "배", price: 1500, quantity: 5 },
];

const tatal_quantity = (fruits) =>
  go(
    fruits,
    map((item) => item.quantity),
    reduce((a, b) => a + b),
    console.log
  );

const tatal_quantity_pipe = pipe(
  map((item) => item.quantity),
  reduce((a, b) => a + b),
  console.log
);

tatal_quantity(fruits); // 15
tatal_quantity_pipe(fruits); // 15

 

한번 더 추상화 시키기

const add = (a, b) => a + b;
const sum = (f, iter) => go(iter, map(f), reduce(add));

const tatal_quantity_pipe = (fruits) => sum((item) => item.quantity * item.price, fruits);

console.log(tatal_quantity_pipe(fruits)); // 41500

 

curry로 감싸기

const curry = (f) =>(a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

const add = (a, b) => a + b;

const sum = curry((f, iter) => go(iter, map(f), reduce(add)));

const tatal_quantity_pipe = sum((item) => item.quantity * item.price); // 코드 간소화 완료

console.log(tatal_quantity_pipe(fruits)); // 41500

curry로 함수를 감싸서 코드를 간소화 시킬 수 있다.

 

 

다형성을 이용하여 table로 결과 출력하기

const fruits = [
  { name: "사과", price: 1000, quantity: 1 },
  { name: "오렌지", price: 2000, quantity: 2 },
  { name: "포도", price: 3000, quantity: 3 },
  { name: "파인애플", price: 5000, quantity: 4 },
  { name: "배", price: 1500, quantity: 5 },
];

const add = (a, b) => a + b;
const sum = curry((f, iter) => go(iter, map(f), reduce(add)));

const tatal_quantity = sum((item) => item.quantity);
const tatal_price = sum((item) => item.quantity * item.price);

document.querySelector("#cart").innerHTML = `
  <table>
    <tr>
      <td>상품이름<td>
      <td>가격<td>
      <td>수량<td>
      <td>총가격<td>
    </tr>

    ${go(
      fruits,
      sum(
        (item) => `
        <tr>
          <td>${item.name}<td>
          <td>${item.price}<td>
          <td>${item.quantity}<td>
          <td>${item.price * item.quantity}<td>
        </tr>
      `
      )
    )}
    <tr>
      <td>합계<td>
      <td><td>
      <td>${tatal_quantity(fruits)}<td>
      <td>${tatal_price(fruits)}<td>
    </tr>
  </table>
`;

sum으로 값을 만들어 낼 수도 있지만 태그들을 반환할 수도 있다.

 

지연 평가(Laze Evaluation)

- 지연평가 = 제때 계산법 = 느긋한 계산법

- 제너레이터/이터레이터 프로토콜을 기반으로 구현

- 제너레이터로 생성한 이터레이터를 만나서 값을 꺼낼 필요가 생길 때 까지 평가를 미루는 기법.

 

range 함수 만들기

0부터 n까지의 값을 담는 배열을 만드는 range 함수와

generator를 사용하여 만든 느긋한 L.range를 비교 해보기 

const range = (l) => {
  const res = [];
  let i = -1;
  while (++i < l) {
    res.push(i);
  }
  return res;
};

const list = range(5);

console.log(list); //  [0, 1, 2, 3, 4] *이미 평가가되어 값이 만들어짐
console.log(reduce((a, b) => a + b, list)); // 10

const L = {};
L.range = function* (l) {
  let i = -1;
  while (++i < l) {
    yield i;
  }
};

const list2 = L.range(5);

console.log(list2); // L.range {<suspended>}
// *아직 순회가 돌지 않은 상태이고
// L.range내부의 함수는 이터레이터를 순회할 때 실행된다.

console.log(reduce((a, b) => a + b, list2)); // 10

range와 L.range의 reduce로 반환된 값은 같지만

range의 list는 값이 만들어진 상태이고 (reduce에서 iterator로 변환)

generator로 만든 iterator인 L.range는 순회가 돌기 전까지는 함수 내부가 실행되지 않는다.

 

효율성 측면에서는 L.range가 더 좋다.

 

 

take함수로 알아보는 효율성

주어진 값만큼 잘라서 반환하는 take함수로 효율성을 알아볼 수 있다.

const take = (l, iter) => {
  let res = [];
  for (const a of iter) {
    res.push(a);
    if (res.length == l) return res;
  }
  return res;
};

console.time("");
console.log(take(5, range(9999999))); // 196.229ms
console.timeEnd("");

console.time("");
console.log(take(5, L.range(9999999))); // 0.075ms
console.timeEnd("");

range함수는 9999999의 값을 가지고 평가된 배열을 만들고 take를 이용해서 5개를 자르게 된다.

하지만 L.range 함수는 순회를 돌 때 실행되기 때문에 5번만 순회를 하게된다.

무한대의 값인 Infinity값을 넣더라도 take에 작성한 값만큼만 순회를 한다.

 

L.map / L.filter

L.map = function* (f, iter) {
  for (const a of iter) yield f(a);
};

const it = L.map((a) => a + 1, [10, 20, 30]); // 아직 평가되지 않음

console.log(it.next()); // {value: 11, done: false}
console.log(it.next()); // {value: 21, done: false}
console.log(it.next()); // {value: 31, done: false}

L.filter = function* (f, iter) {
  for (const a of iter) if (f(a)) yield a;
};

const it2 = L.filter((a) => a > 10, [10, 20, 30]); // 아직 평가되지 않음
console.log([...it2]); // [20, 30]

L.map과 L.filter 또한 값을 꺼낼 필요가 있을 때 내부의 함수를 실행하게 된다.

 

 


느낀 점

 

드디어 알고리즘 파트가 오늘로서 끝났다..

아직 높은 레벨의 문제들은 풀질 못해서 차근차근 낮은단계부터 꾸준하게 문제를 풀어보며 익힐 예정이다..

함수형 프로그래밍 파트에서 지연평가 기법을 따라서 치다보니 이해가 약간은 된 듯 했다.

이전엔 generator는 왜 쓸까? 했는데 이런 효율성 때문에 사용할 수도 있겠구나~하는 이해가 생겼다.

한 줄씩 평가하고 넘어가는 방식이 아닌 새로운 방식의 평가 순서도 아주 인상 깊었다.😲👍

하지만 어떤 식으로 응용이 가능할 지 감이 잘 잡히지 않는다.

보는 것과 활용하는 것은 다르기 때문에 함수형 사고를 조금씩 더 키워 나가야겠다!

Comments