Experience/[Javascript] Calculator

[Javascript] Calculator ⑤Completion

winCow 2021. 4. 30. 16:53

1. 완성

만들다 보니 꽤 많이 뜯어고치게 되었다.

마지막에 키 입력을 인식하도록 하는데, 이벤트 리스너만 추가하면 될 줄 알았더니 생각보다 문제가 많이 발생했다.

 

 

2. Refactoring

const inputs = document.querySelectorAll('.keyButtons');

let numStorage = [];
let givenKeys = [];
let result = 0;
let index = 0;

inputs.forEach(input => input.addEventListener('click', getValue));
inputs.forEach(input => input.addEventListener('click', displayProcess));
window.addEventListener("keydown", checkKeyValue);

먼저 각 함수에서 공유할 전역변수들과 이벤트 리스너들을 선언하였다. 마우스 클릭으로 값을 입력받아 계산하는 것을 기본으로 하고, 이후 키보드로 입력받을 수 있게 수정하였다.

 

function getValue(e) {
  let keyValue = this.value || e.key;
  let givenValue = parseFloat(givenKeys.join(''));
  if (keyValue == '+'
  || keyValue == '-'
  || keyValue == '×'
  || keyValue == '*'
  || keyValue == '÷'
  || keyValue == '/'
  || keyValue == '='
  || keyValue == 'Enter') {
    numStorage.push(
      {
        value: givenValue,
        calculator: keyValue
      });
    saveAndCalc(keyValue);
  } else if (keyValue == "C" || keyValue == "Escape") {
    numStorage = [];
    result = 0;
    givenKeys = [result];
  } else if (keyValue == "CE") {
    givenKeys = [0];
  } else if (keyValue == "%") {
    givenValue = givenValue * 0.01;
    givenKeys = [];
    givenKeys.push(givenValue);
  } else if (keyValue == "+/-") {
    givenValue = givenValue * -1;
    givenKeys = [];
    givenKeys.push(givenValue);
  } else {
    givenKeys.push(keyValue);
    givenKeys.splice(-8, givenKeys.length - 7);
  }
}

마우스 클릭에 반응하는 getValue는 위와 같다. 마우스 클릭 이벤트로 전달받은 this.value 혹은 키 다운 이벤트로 전달받은 e.key를 keyValue에 할당하고, givenValue에는 givenKeys 배열을 숫자로 만들어 할당한다. 만약 전달받은 값인 keyValue가 사칙연산이나 =, enter에 해당하는 경우 이전까지 givenKeys에 저장된 값을 numStorage에 객체 형태로 옮기고, saveAndCalc를 실행시킨다. keyValue가 C나 ESC인 경우에는 numStorage, result, givenKeys를 모두 초기화한다. %를 받는 경우에는 저장된 숫자에 0.01을 곱해서 반환하고, +/-를 받는 경우에는 -1을 곱해서 반환한다. 마지막으로 그 외의 숫자에 대해서는 최대 7자리까지 givenKeys에 저장한다.

 

function saveAndCalc(calculator) {
    givenKeys = [];
  if (index == 0) {
    result = numStorage[0]['value'];
  } else {
    if (numStorage[index - 1]['calculator'] == '+') {
      result = result + numStorage[index]['value'];
    } else if (numStorage[index - 1]['calculator'] == '-') {
      result = result - numStorage[index]['value'];
    } else if (numStorage[index - 1]['calculator'] == '×'
    || numStorage[index - 1]['calculator'] == '*') {
      result = result * numStorage[index]['value'];
    } else if (numStorage[index - 1]['calculator'] == '÷'
    || numStorage[index - 1]['calculator'] == '/') {
      result = result / numStorage[index]['value'];
    }
  }
  index = index + 1;
  if (calculator == '=' || calculator == "Enter") {
    numStorage = [];
    givenKeys.push(result);
    index = 0;
  }
  displayPreview(result);
}

getValue에서 콜백된 함수 saveAndCalc는 위와 같다. 원리 자체는 이전에 만든 것과 같으나, 키보드 입력에 대응하기 위해 '*', '/'에도 반응하도록 조건문을 수정하였다. 또, result를 displayPreview에 전달하여 preview 창에 중간 계산 과정이 표시되도록 했다.

 

function displayPreview(result) {
  let preview = document.querySelector('.preview');
  preview.textContent = result;
}

위와 같이 간단하게 중간 계산 과정을 표시하는 콜백함수를 만들었다.

 

function displayProcess(e) {
  const process = document.querySelector('.process');
  if (this.value == '+'
  || this.value == '-'
  || this.value == '×'
  || this.value == '÷'
  || e.key == '+'
  || e.key == '-'
  || e.key == '*'
  || e.key == '/') {
    return ;
  } else {
    process.textContent = parseFloat(givenKeys.join(''));
  }
}

인식된 값을 화면에 표시하는 기능을 가진 displayProcess 함수 역시 간단하게 e.key에도 대응할 수 있도록 수정했다.

 

function checkKeyValue(e) {
  if ((0 <= parseInt(e.key) && parseInt(e.key) <= 9 )
  || e.key == '.'
  || e.key == 'Enter'
  || e.key == '='
  || e.key == 'C'
  || e.key == 'Escape'
  || e.key == '%'
  || e.key == '+'
  || e.key == '-'
  || e.key == '*'
  || e.key == '/') {
    getValue(e);
    displayProcess(e);
  } else return ;
}

많은 시간을 할애하게 된 것이 이 부분이었다. 인식하고 싶은 키 외에는 전부 아무것도 반환하지 않도록 했고, 인식하고 싶은 키는 getValue와 displayProcess에 전달하도록 했다.

 

3. 반성

 - 키 다운 이벤트를 통해 전달되는 이벤트와 마우스 클릭 이벤트를 통해 전달되는 이벤트를 동일하게 취급하다 보니 문제가 많이 발생했었다. 키보드를 통해서 전달하는 값은 해당 이벤트의 key 값을 사용해야 했고, 마우스 클릭을 통해 전달하는 값은 this인 input 태그의 속성 값을 사용해야 했다. 이번에는 이 두 가지 값을 구분해서 사용했지만, 글을 작성하다 보니 키 다운 이벤트와 input 태그를 묶는 작업을 먼저 했으면 어땠을까 한다. 그랬다면 위의 함수들을 대대적으로 공사하지 않고도 문제를 해결할 수 있지 않았을까? 나중에 한 번 더 수정을 해 봐야겠다. (this.value !== e.key)

 - 처음부터 순서도를 짜서 만들었으면 코드를 더 깔끔하게 만들 수 있었을 것 같다. 이 경우에도, 각각의 버튼을 입력 받는 것만을 생각했는데, 화면에 표시되는 버튼(숫자와 C, %, . 등), 화면에 표시되지는 않지만 기능을 수행하는 버튼(사칙연산과 =)으로 나누어 로직을 구성했는데, 처음에는 숫자와 연산자로 구분하여 로직을 짜려다가 헤맸던 기억이 있다. 코드를 짜면서 로직을 수정하는 것이 낫겠다고 생각하게 되었는데, 순서도를 생략해서 이렇게 된 것 같다.