[JavaScript] 계산기

Lpla

·

2021. 6. 20. 19:46

반응형

이 글은 유튜브 ZeroCho ES2021 자바스크립트 강좌에 나온 내용을 기초로 작성했지만 다른 부분이 존재합니다.

 

1. HTML 마크업, CSS 추가

자바스크립트로 단순한 사칙연산 계산기를 만들어보겠다.

실제 계산기는 신경 써야 할 게 훨씬 많고 복잡하기 때문에 여기서는 다루지 않는다.

먼저 HTML과 CSS는 아래 내용을 그대로 복사한다.

 

<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./style.css">
</head>

<body>
  <input readonly id="operator">
  <input readonly type="number" id="result">
  <div class="row">
    <button id="num-7">7</button>
    <button id="num-8">8</button>
    <button id="num-9">9</button>
    <button id="plus">+</button>
  </div>
  <div class="row">
    <button id="num-4">4</button>
    <button id="num-5">5</button>
    <button id="num-6">6</button>
    <button id="minus">-</button>
  </div>
  <div class="row">
    <button id="num-1">1</button>
    <button id="num-2">2</button>
    <button id="num-3">3</button>
    <button id="divide">/</button>
  </div>
  <div class="row">
    <button id="clear">C</button>
    <button id="num-0">0</button>
    <button id="calculate">=</button>
    <button id="multiply">X</button>
  </div>
  <script src="./js/script.js"></script>
</body>

</html>
* { box-sizing: border-box; }
#result { width: 180px; height: 50px; margin: 5px; text-align: right; }
#operator { width: 50px; height: 50px; margin: 5px; text-align: center; }
button { width: 50px; height: 50px; margin: 5px; }

 

간단한 계산기 레이아웃이 완성됐다.

좌측상단 input 요소는 클릭한 연산자가 표시되고 우측상단 input 요소는 클릭한 숫자가 표시된다.

 

자바스크립트로 계산 기능을 추가하기 전에, 먼저 작동 순서를 생각해보자.

 

 

2. 순서

계산기는 숫자1 + 연산자 + 숫자2 = 결과값으로 이루어진다.

이것을 풀어서 설명하면 ‘1. 숫자를 클릭한다. 2. 연산자(= 제외)를 클릭한다. 3. =을 클릭한다.’ 와 같다.

따라서 기본적으로 세 가지 변수가 필요하다. 숫자1은 numOne, 숫자2는 numTwo, 연산자는 operator로 정했다.

 

2-1. 숫자 클릭

숫자를 클릭했을 때 operator 변수에 값이 없다면 해당 숫자는

numOne 변수에 할당되고 operator 변수에 값이 있다면 numTwo 변수에 할당된다.

숫자를 두 개 이상 클릭한다면 여러 자리 숫자를 만들기 위해 숫자들을 나란히 붙여 변수에 할당한다.

 

2-2. 연산자 클릭

연산자를 클릭했을 때 numOne에 값이 없다면 순서가 잘못됐기 때문에 숫자를 먼저 입력하라는 경고창을 띄운다.

numOne의 값이 있고 numTwo에 값이 없다면 해당 연산자를 operator 변수에 할당한다.

numOne과 numTwo에 모두 값이 있다면 연산자를 클릭하더라도 아무런 기능을 하지 않기 때문에 변수를 변경하지 않는다.

 

2-3. = 클릭

=을 클릭했을 때 numOnenumTwo변수가 모두 값을 가지고 있다면 operator 변수로 연산한다.

예를 들어 operator 변수가 * 라면, numOne*numTwo다.

실제로 코딩을 해보면 위 생각대로 순조롭게 작동하지 않을 수 있다.
그 점은 염려해두고 자바스크립트를 작성하자.

 

 

3. 변수 선언 및 숫자 이벤트

let numOne = '';
let operator = '';
let numTwo = '';
const $operator = document.querySelector('#operator');
const $result = document.querySelector('#result');

먼저 변수를 선언하고 빈 값을 할당한다.

operator 변수와 $operator변수가 있어서 두 개가 헷갈릴 수 있는데, operator 변수는 실제 연산자(+, -, *, /)를 할당할 변수고 $operator변수는 html input 요소를 가리키는 변수로, 입력한 연산자를 화면에 표시하는데 사용한다.

 

zerocho님께서는 html 요소를 변수로 사용할 경우 앞에 $를 붙여 헷갈리지 않게 한다고 한다.

괜찮은 방법 같아서 나도 이번에 사용해봤다.

 

이제 숫자 버튼을 클력하면 해당 숫자가 변수에 할당돼야 한다.

0을 클릭하면 0이 할당되고, 1을 클릭하면 1이 할당되고.... 9를 클릭하면 9가 할당되는 방식이다.

이때 이벤트 리스너를 사용한다.

이벤트 리스너는 종류가 굉장히 많은데 다음 사이트를 참고하면 좋다.

 

이벤트 참조 | MDN

DOM 이벤트는 발생한 흥미로운 것을 코드에 알리기 위해 전달됩니다. 각 이벤트는 Event 인터페이스를 기반으로한 객체에 의해 표현되며 발생한 것에 대한 부가적인 정보를 얻는데 사용되는 추가

developer.mozilla.org

그리고 숫자만 다를 뿐 작동하는 기능은 같기 때문에 함수로 묶어 사용하도록 한다.

 

const onClickNumber = (number) => () => {
  if (operator) {
    numTwo += number;
  } else {
    numOne += number;
  }
  console.log(numOne, numTwo);
  $result.value += number;
}

document.querySelector('#num-0').addEventListener('click', onClickNumber('0'));
document.querySelector('#num-1').addEventListener('click', onClickNumber('1'));
document.querySelector('#num-2').addEventListener('click', onClickNumber('2'));
document.querySelector('#num-3').addEventListener('click', onClickNumber('3'));
document.querySelector('#num-4').addEventListener('click', onClickNumber('4'));
document.querySelector('#num-5').addEventListener('click', onClickNumber('5'));
document.querySelector('#num-6').addEventListener('click', onClickNumber('6'));
document.querySelector('#num-7').addEventListener('click', onClickNumber('7'));
document.querySelector('#num-8').addEventListener('click', onClickNumber('8'));
document.querySelector('#num-9').addEventListener('click', onClickNumber('9'));

각각 숫자 버튼에 클릭 이벤트를 생성하고, 클릭 시 onClickNumber 함수가 실행된다.

onClickNumber 함수는 operator 변수에 값이 없다면 numOne에 숫자를 할당하고, 있다면 numTwo에 할당하는 기능을 하는 함수다.

그리고 클릭한 숫자를 확인할 수 있도록 result 요소에 숫자를 표시하도록 했다.

 

 

4. 연산자 이벤트

연산자 이벤트도 같은 방식으로 만들 수 있다.

연산자를 클릭했을 때 호출되는 함수는 onClickOperator로 지었다.

 

const onClickOperator = (op) => () => {
  operator = op;
  $operator.value = op;
  if (numOne && !numTwo) {
    $result.value = '';
  } else if (operator == 'C') {
    console.log('reset');
    $operator.value = '';
    $result.value = '';
    numOne = operator = numTwo = '';
  } else if (!numOne) {
    alert('숫자를 먼저 입력하세요.');
  }
}

document.querySelector('#plus').addEventListener('click', onClickOperator('+'));
document.querySelector('#minus').addEventListener('click', onClickOperator('-'));
document.querySelector('#divide').addEventListener('click', onClickOperator('/'));
document.querySelector('#multiply').addEventListener('click', onClickOperator('*'));
document.querySelector('#clear').addEventListener('click', onClickOperator('C'));

 

onClickOperator 함수를 호출하면 매개변수 op를 앞서 만든 operator 변수에 할당하고

numOne의 값이 있고 numTwo의 값이 없을 때 매개변수 op를 우측상단 input 요소의 value 값으로 입력한다.

어렵게 이해할 것 없이 위에서 언급한 숫자1 + 연산자 + 숫자2 = 결과값에서 숫자1 + 연산자를 의미한다.

그리고 C는 clear 기능이므로 다른 연산자와 달리 클릭하면 모든 값을 초기화한다.

그리고 numOne의 값이 없다면 숫자를 입력하라는 경고 메시지를 띄운다.

 

 

5. 계산

이제 마지막 결과값을 계산하는 단계다.

입력한 연산자에 따라서 숫자1과 숫자2를 더하기, 빼기, 곱하기, 나누기 중에서 실행하면 된다.

 

if (operator == '+') {
  $result.value = parseInt(numOne) + parseInt(numTwo);
  console.log($result.value);
} else if (operator == '-') {
  $result.value = parseInt(numOne) - parseInt(numTwo);
  console.log($result.value);
} else if (operator == '*') {
  $result.value = parseInt(numOne) * parseInt(numTwo);
  console.log($result.value);
} else if (operator == '/') {
  $result.value = parseInt(numOne) / parseInt(numTwo);
  console.log($result.value);
}

 

개인적으로 switch문보다 if문을 훨씬 선호하지만 switch문으로 더 깔끔하게 표기할 수 있다.

 

switch (operator) {
  case '+':
    $result.value = parseInt(numOne) + parseInt(numTwo);
    console.log($result.value);
    break;
  case '-':
    $result.value = parseInt(numOne) - parseInt(numTwo);
    console.log($result.value);
    break;
  case '*':
    $result.value = parseInt(numOne) * parseInt(numTwo);
    console.log($result.value);
    break;
  case '/':
    $result.value = parseInt(numOne) / parseInt(numTwo);
    console.log($result.value);
    break;
  default:
    break;
}

 

 

6. 오류 해결

계산기에 필요한 기능을 모두 만들었다.

이제 마지막으로 점검을 해보자.

 

연산자도 잘 작동하고 Clear도 잘 먹히는 것처럼 보인다.

하지만 한 가지 오류가 존재한다.

 

숫자1 + 연산자 + 숫자2 = 결과값 순서에서 숫자2까지 입력하고 다시 연산자를 입력하면 이전 연산자가 덮어 씌워지게 된다.

원인은 onClickOperator 함수 내부의 조건문 외부에 operator = op; 가 정의되어 있기 때문이다.

 

이것을 조건문 내부로 이동시켜야 한다.

하지만 그렇게 되면 Clear 기능이 작동하지 않는다.

생각해보면 Clear 기능은 다른 연산자와는 하는 역할이 다르기 때문에 다른 함수로 빼주는 것이 좋은 것 같다.

 

const onClickClear = () => () => {
  console.log('reset');
  $operator.value = '';
  $result.value = '';
  numOne = operator = numTwo = '';
}

 

그리고 원래 onClickOperator 함수도 수정했다.

 

const onClickOperator = (op) => () => {
  if (numOne && !numTwo) {
    operator = op;
    $operator.value = op;
    $result.value = '';
  } else if (!numOne) {
    alert('숫자를 먼저 입력하세요.');
  }
}

 

이제 정상적으로 작동된다.

 

전체 코드

 

index.html

<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./style.css">
</head>

<body>
  <input readonly id="operator">
  <input readonly type="number" id="result">
  <div class="row">
    <button id="num-7">7</button>
    <button id="num-8">8</button>
    <button id="num-9">9</button>
    <button id="plus">+</button>
  </div>
  <div class="row">
    <button id="num-4">4</button>
    <button id="num-5">5</button>
    <button id="num-6">6</button>
    <button id="minus">-</button>
  </div>
  <div class="row">
    <button id="num-1">1</button>
    <button id="num-2">2</button>
    <button id="num-3">3</button>
    <button id="divide">/</button>
  </div>
  <div class="row">
    <button id="clear">C</button>
    <button id="num-0">0</button>
    <button id="calculate">=</button>
    <button id="multiply">X</button>
  </div>
  <script src="./js/script.js"></script>
</body>

</html>

 

style.css

* { box-sizing: border-box; }
#result { width: 180px; height: 50px; margin: 5px; text-align: right; }
#operator { width: 50px; height: 50px; margin: 5px; text-align: center; }
button { width: 50px; height: 50px; margin: 5px; }

 

script.js

let numOne = '';
let operator = '';
let numTwo = '';
const $operator = document.querySelector('#operator');
const $result = document.querySelector('#result');

const onClickNumber = (number) => () => {
  if (operator) {
    numTwo += number;
  } else {
    numOne += number;
  }
  console.log('numOne : ' + numOne, 'numTwo : ' + numTwo, 'operator : ' + operator);
  $result.value += number;
}

const onClickOperator = (op) => () => {
  if (numOne && !numTwo) {
    operator = op;
    $operator.value = op;
    $result.value = '';
  } else if (!numOne) {
    alert('숫자를 먼저 입력하세요.');
  }
}

const onClickClear = () => () => {
  console.log('reset');
  $operator.value = '';
  $result.value = '';
  numOne = operator = numTwo = '';
}

const onClickCalculator = () => () => {
  if (operator == '+') {
    $result.value = parseInt(numOne) + parseInt(numTwo);
    console.log($result.value);
  } else if (operator == '-') {
    $result.value = parseInt(numOne) - parseInt(numTwo);
    console.log($result.value);
  } else if (operator == '*') {
    $result.value = parseInt(numOne) * parseInt(numTwo);
    console.log($result.value);
  } else if (operator == '/') {
    $result.value = parseInt(numOne) / parseInt(numTwo);
    console.log($result.value);
  }
}

document.querySelector('#num-0').addEventListener('click', onClickNumber('0'));
document.querySelector('#num-1').addEventListener('click', onClickNumber('1'));
document.querySelector('#num-2').addEventListener('click', onClickNumber('2'));
document.querySelector('#num-3').addEventListener('click', onClickNumber('3'));
document.querySelector('#num-4').addEventListener('click', onClickNumber('4'));
document.querySelector('#num-5').addEventListener('click', onClickNumber('5'));
document.querySelector('#num-6').addEventListener('click', onClickNumber('6'));
document.querySelector('#num-7').addEventListener('click', onClickNumber('7'));
document.querySelector('#num-8').addEventListener('click', onClickNumber('8'));
document.querySelector('#num-9').addEventListener('click', onClickNumber('9'));
document.querySelector('#plus').addEventListener('click', onClickOperator('+'));
document.querySelector('#minus').addEventListener('click', onClickOperator('-'));
document.querySelector('#divide').addEventListener('click', onClickOperator('/'));
document.querySelector('#multiply').addEventListener('click', onClickOperator('*'));
document.querySelector('#clear').addEventListener('click', onClickClear());
document.querySelector('#calculate').addEventListener('click', onClickCalculator());

 

결과값을 가지고 다시 한 번 더 계산하기, 음수값으로 계산하기 등은 스스로 학습해보길 권장한다.

 

반응형