What I Learned: Level1 javascript-lotto

동동·2021년 4월 25일
2

0. PR 링크

1. 학습로그

1. Map vs Object

Object는 Map과 비슷합니다. 두 가지 모두 키를 값으로 설정하고, 해당 값을 검색하고, 키를 삭제하고, 어떤 항목이 키에 저장되어 있는지 여부를 탐지할 수 있습니다.

이러한 이유로(그리고 내장된 대안이 없었기 때문에), Object는 역사적으로 Map으로 사용되었습니다. 그러나 Map을 선호하는 중요한 차이점이 있습니다.

PropertyMapObject
의도치 않은 키Map은 기본적으로 어느 키도 포함하고 있지 않습니다. 오직 명시적으로 제공한 키만 포함합니다"Object는 프로토타입을 가지고 있으므로, 기본키를 가지고 있으며, 주의하지 않는다면 당신의 키와 충돌할 수 있습니다.
Note: ES5부터 프로토타입으로 인한 키 충돌은 Object.create(null)로 해결될 수 있지만, 실제로 쓰이는 경우는 적습니다."
키 자료형Map의 키로는 어떤 자료형이든 가능합니다. (함수, 객체 또는 어떤 기본형이든)Object의 키는 반드시 String 또는 Symbol이어야 합니다.
키 순서Map 의 키는 간단하고 직관적인 방법으로 정렬됩니다. Map 객체는 삽입된 순서대로 순회합니다.일반적인 Object의 키는 지금 정렬되지만, 이전에는 정렬되지 않았으며, 그 순서는 복잡합니다. 결과적으로 속성 순서에 의존하지 않는 것이 최선입니다.
크기Map에 포함된 아이템 수는 size 속성을 통해 쉽게 가지고 올 수 있습니다.Object에 포함된 아이템 수는 수동으로 알아내야 합니다.
순회(Iteration)Map은 순회가능(iterable)하므로 바로 순회할 수 있습니다.Object는 기본적으로 iteration protocol이 구현되어 있지 않으므로 직접적으로 for...of 문을 사용하여 순회할 수 없습니다.
Note
• Object는 iteration protocol을 구현할 수 있으며, Object.keys 또는 Object.entries를 사용하여 Object에 대한 iterable을 가져올 수 있습니다.
• for...in문은 Object의 enumerable 속성에 대하여 순회가능하게 해줍니다.
성능잦은 키-값 쌍의 추가와 제거가 포함된 경우에 더 좋은 성능을 보입니다.잦은 키-값 쌍의 추가와 제거가 포함된 경우에 대하여 최적화되어 있지 않습니다.
JSON.stringifyJSON.stringify에서는 Map을 무시합니다. 따라서 replace 함수를 명시해주어야 합니다.중괄호로 감싸진 키:값 의 형태로 문자열화됩니다.

Map vs Object

Map

2. WeakMap 을 활용한 caching

1. WeakMap이란?

WeakMap의 기능은 Map과 동일합니다.

차이점1. 키로 쓰인 객체가 가비지 컬렉션의 대상이 되는지 여부가 다릅니다.

자바스크립트 엔진은 도달 가능한 (그리고 추후 사용될 가능성이 있는) 값을 메모리에 유지합니다. 자료구조를 구성하는 요소도 자신이 속한 자료구조가 메모리에 남아있는 동안 대개 도달 가능한 값으로 취급되어 메모리에서 삭제되지 않습니다. 객체의 프로퍼티나 배열의 요소, 맵이나 셋을 구성하는 요소들이 이에 해당합니다. 맵에서 객체를 키로 사용한 경우, 맵이 메모리에 있는 한 객체도 메모리에 남아, 가비지 컬렉터의 대상이 되지 않습니다.
위크맵(WeakMap)은 일반 맵과 전혀 다른 양상을 보입니다. 위크맵을 사용하면 키로 쓰인 객체가 가비지 컬렉션의 대상이 됩니다. 따라서 위크맵의 키로 사용된 객체를 참조하는 것이 아무것도 없다면 해당 객체는 메모리와 위크맵에서 자동으로 삭제됩니다.

차이점 2. 맵과 위크맵의 두 번째 차이는 위크맵은 반복 작업과 keys(), values(), entries() 메서드를 지원하지 않는다는 점입니다.

따라서 위크맵에선 키나 값 전체를 얻는 게 불가능합니다. 왜 이렇게 적은 메서드만 제공할까요? 원인은 가비지 컬렉션의 동작 방식 때문입니다. 객체는 모든 참조를 잃게 되면 자동으로 가비지 컬렉션의 대상이 됩니다. 그런데 가비지 컬렉션의 동작 시점은 정확히 알 수 없습니다.
가비지 컬렉션이 일어나는 시점은 자바스크립트 엔진이 결정합니다. 객체는 모든 참조를 잃었을 때, 그 즉시 메모리에서 삭제될 수도 있고, 다른 삭제 작업이 있을 때까지 대기하다가 함께 삭제될 수도 있습니다. 따라서 현재 위크맵에 요소가 몇 개 있는지 정확히 파악하는 것 자체가 불가능한 것이죠. 가비지 컬렉터가 한 번에 메모리를 청소할 수도 있고, 부분 부분 메모리를 청소할 수도 있으므로 위크맵의 요소(키/값) 전체를 대상으로 무언가를 하는 메서드는 동작 자체가 불가능합니다.

2. 활용방법

1. 추가 데이터

추가할 데이터가 객체가 살아있는 동안에만 유효한 상황에서 사용할 수 있다. 위크맵에 원하는 데이터를 저장하고, 이때 키는 객체를 사용하면 됩니다. 이렇게 하면 객체가 가비지 컬렉션의 대상이 될 때, 데이터도 함께 사라지게 됩니다.

2. 캐싱

. 캐싱은 시간이 오래 걸리는 작업의 결과를 저장해서 연산 시간과 비용을 절약해주는 기법입니다. 동일한 함수를 여러 번 호출해야 할 때, 최초 호출 시 반환된 값을 어딘가에 저장해 놓았다가 그다음엔 함수를 호출하는 대신 저장된 값을 사용하는 게 캐싱의 실례입니다.위크맵을 활용하여 결과 값을 저장해두면 복잡한 연산을 한번만 수행해도 됩니다. 또한 위크맵의 키인 객체가 메모리에서 삭제되면, 캐시에 저장된 결과(함수 연산 결과) 역시 메모리에서 자동으로 삭제되므로 메모리 릭이 발생하지 않습니다.

3. HTMLElement Custom Method 추가

  1. document.querySelector를 커스텀한 $ 함수를 통하여 반환되는 객체를 통하여, 커스텀으로 추가한 메서드도 HTMLElement의 기존 프로퍼티와 동일하게 사용하고 싶다는 생각에서 이 모든 것이 출발되었습니다.
const $ = (selector) => some_kind_of_wrapping(document.querySelector(selector));
const $div = $("div");
$div.innerText = "Test";      // innerText: HTMLElement 의 기존 프로퍼티
$div.hide();                  // hide: 커스텀 메서드
  1. 처음에 떠올린 방안은 HTMLElement 함수의 prototype에 mixin을 활용하여 커스텀메서드를 추가하는 방법이었습니다.
// CSS --hidden 클래스를 제거하면 HTMLElement가 안보이게 됩니다
Object.assign(HTMLElement.prototype, { hide: this.classList.add("--hidden") }); 

$div.hide();
  1. 내장 객체인 HTMLElement의 prototype에 직접 프로퍼티를 추가하는 것은 아래와 같은 문제점이 있는 것으로 생각됩니다.

    • 의도하지 않게 내장 객체의 프로퍼티를 override 하게 되어 malfunction 이 일어날 수 있습니다.
    • Possible conflicts: 프로토타입은 전역으로 영향을 미치기 때문에 프로토타입을 조작하면 충돌이 날 가능성이 높습니다. 두 라이브러리에서 동시에 HTMLElement.prototype.hide 메서드를 추가하면 한 라이브러리의 메서드가 다른 라이브러리의 메서드를 덮어쓰게 됩니다.
  2. 클로져와 커스텀 메서드가 추가된 래퍼 객체를 $ 함수의 반환값으로 하여 이를 해결할 수 있습니다. 하지만 이러한 방법은, 해당 프로그램에서 HTMLElement로 수행하는 모든 프로퍼티를 래퍼 객체에 일일히 추가해야 한다는 단점이 있습니다.

  3. Proxy를 활용해보자!

    1. Proxy는 특정 객체를 감싸 프로퍼티 읽기, 쓰기와 같은 객체에 가해지는 작업을 중간에서 가로채는 객체로, 가로채진 작업은 Proxy 자체에서 처리되기도 하고, 원래 객체가 처리하도록 그대로 전달되기도 합니다.
      Proxy는 일반 객체와는 다른 행동 양상을 보이는 '특수 객체(exotic object)'입니다. 프로퍼티가 없죠. handler가 비어있으면 Proxy에 가해지는 작업은 target에 곧바로 전달됩니다.

Proxy를 활용하면 3. 에서 언급한 Possible conflicts는 회피 가능할 것으로 생각되고, 커스텀 메서드 작성시 추가하고자 하는 메서드와 동일한 명칭을 가진 내장 객체의 프로퍼티가 있는지를 확인하는 것으로 회피 가능할 것으로 생각됩니다.

Reference

=> 답변
이렇게 딥하게 생각하신거에 놀랍네요! ㅎㅎㅎ 실무에서도 이정도까지는 하고 있지는 않습니다.
사실 기본 제공되는 기능에 합치는 것이 아니라, 제공되는 객체를 래핑해서 커스텀 객체를 만드는게 더 좋았을 것 같아요! 이런 함수들이 내장객체에 들어있다는 것을 모든 동료들이 알아야하는 것도 그렇고 코드의 뜻을 파악하고 유추하기는 쉽지 않을 것 같아요.
util 성 클래스에서 기능을 제공하는 것도 더 쉽고 가시적으로 보이고 나쁘지 않았을 것 같아요.
그럼에도 이런 도전 좋은데요?!

4. Input Validation 추상화

input으로부터 값을 가지고 와서 검증하고 정해진 액션을 실행하는 패턴이 지속적으로 반복되고 있다는 것을 깨달았습니다. 이를 아래와 같이 추상화하여 해당되는 부분에 모두 동일하게 적용시키려고 하였습니다. 아래의 패턴을 적용하기 위하여 내장 Error객체를 상속하는 Custom Error클래스를 만들었습니다.

  1. [read] input Element로부터 값(value)을 원하는 타입으로 가지고 옵니다. 원하는 타입이 아닌 경우 Error를 던집니다.
  2. [validate] 해당 값이 유효한 값인지를 검증합니다. 유효하지 않은 값인 경우 Error를 던집니다.
  3. [action] 해당 값이 유효한 값인 경우, 관련된 액션을 수행합니다.
  4. [catch error]
    • catch 문에서 던져진 Error가 1.[read]와 2.[validate]에서 던져진 Error인 경우, 에러메세지를 alert를 통하여 유저에게 보여줍니다.
    • 그 외의 Error인 경우 다시 Error를 상위로 던집니다.

Reference

5. 웹접근성: HTMLInputElement

모든 HTMLInputElement은 대응되는 HTMLLabelElement를 가지고 있어야 합니다.
스크린 리더기에서는 해당 input에 대응되는 label을 읽어, 해당 input의 의미를 유저에게 전달합니다.
input에 label을 대응시키는 방법은 아래와 같이 2가지가 있습니다.
1. input의 id를 label의 for attribute의 값으로 지정합니다.
2. label의 자식으로 input을 위치시킵니다.

※ 시각적으로 label이 필요가 없다면, label을 작성은 하되, 아래와 같은 CSS 클래스를 사용하여 시각적으로 제거해주세요. 만약 label에 hidden attribute를 주거나 style="display:none" 을 할 경우, 스크린 리더기는 이를 무시합니다.

https://velog.io/@bigsaigon333/Level1-javascript-racingcar#hidden-attribute
https://www.a11yproject.com/checklist/#forms

추후 논의


## 6. 브라우져의 렌더링 과정
왜 모달의 경우에는 display:none 이 아니라 opacity:0; visibility: hidden 으로 하는지 한번 고민해보면 좋을 것 같아요.
브라우져의 렌더링 과정과 reflow, repaint 에 대해서도 알아보면 좋을 것 같아요

## 7. js-selector
JS 에서만 쓰기 위해서 data-js-selector 속성을 쓴 것은 나쁘지 않은 선택이었다고 생각합니다. 현재 data-js-selector 와 HTMLElement 가 1:1 대응이 되고 있는데, 그렇다면 id 속성과 다른 점은 무엇일까요? 만약에 쓰임새가 같다면 굳이 data-js-selector 를 사용할 필요가 있었을까요? id 속성 사용은 왜 지양하라고 하는지 고민해보면 좋을 것 같아요

## 8. Array.from({ length: lottoCount }) 과 [...Array(lottoCount)] 의 차이는 무엇일까요?

## 9. number.toString() 과 String(number)의 차이에 대해서 고민해보면 좋을 것 같아요.
number가 undefined나 null 인 경우에는 둘의 반환값이 어떤 차이가 있을까요?

## 10. ESM

2. Action Plan

쉬운 코드를 지향하자: 유지보수하기 좋은 코드를 작성하자

  • 있어야 할 곳에 있는 코드
  • 당연한 흐름대로 흘러가는 코드
  • 처음 프로젝트를 보는 사람들이 한번에 이해할 수 있는 코드

Proxy 를 적용해 본 것, Presentational - Container 으로 구분한 것, flux pattern을 도입한 것 모두 교육의 관점에서는 좋았으나, 실제로 유지보수에 좋은 코드였는지는 의문이 든다.

왜 쉬운 코드를 지향해야 하는가? 왜냐면 그것이 유지보수하기 좋은 코드이기 때문이다.
왜 유지보수하기 좋은 코드를 작성해야 하는가? 프로그램은 변경되기 때문이다. 이러한 변경에 유연하고 신속하게 대처할 수 있는 코드가 유지보수하기 좋은 코드라고 할 수 있다.

profile
작은 실패, 빠른 피드백, 다시 시도

3개의 댓글

comment-user-thumbnail
2021년 4월 26일

와 동동님 기술에 대해 이렇게 심도있게 고민하고, 개선하고 액션플랜까지 작성하셔서 감탄하고 갑니다! 이 글 한편만 봐도 프로그래밍에 얼마나 진정성 있으신지 알 수 있는 것 같아요 👍🏼

1개의 답글
comment-user-thumbnail
2021년 4월 27일

동동님과 같이 공부하고 싶네요~! 깊은 지식에 무릎을 탁 치고 갑니다

답글 달기