JS 계산기 만들기

shinyeongwoon·2022년 10월 26일
0

JS

목록 보기
15/16

HTML 작성하기

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=, initial-scale=1.0">
  <title>계산기</title>
</head>
<body>
  <style>
    *{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;}
  </style>

    <input readonly id="operator">
    <input readonly type="number" id="result">
    <div class="row">
      <button id="number-7">7</button>
      <button id="number-8">8</button>
      <button id="number-9">9</button>
      <button id="plus">+</button>
    </div>
    <div class="row">
      <button id="number-4">4</button>
      <button id="number-5">5</button>
      <button id="number-6">6</button>
      <button id="minus">-</button>
    </div>
    <div class="row">
      <button id="number-1">1</button>
      <button id="number-2">2</button>
      <button id="number-3">3</button>
      <button id="div">/</button>
    </div>
    <div class="row">
      <button id="clear">c</button>
      <button id="number-0">0</button>
      <button id="calc">=</button>
      <button id="mul">*</button>
    </div>


</body>
</html>

위 화면과 같이 HTML 을 추가한다
일단 필요한 요소를 선택하고 이벤트 리스너들을 달아준다.

const $result = document.querySelector('#result');
const $operator = document.querySelector('#operator');
      
const clickBtn = (event) => {
  $result.value = event.target.textContent;
};

document.querySelector('#number-0').addEventListener('click',clickBtn);
document.querySelector('#number-1');
document.querySelector('#number-2');
document.querySelector('#number-3');
document.querySelector('#number-4');
document.querySelector('#number-5');
document.querySelector('#number-6');
document.querySelector('#number-7');
document.querySelector('#number-8');
document.querySelector('#number-9');
document.querySelector('#plus');
document.querySelector('#minus');
document.querySelector('#div');
document.querySelector('#mul');
document.querySelector('calc');
document.querySelector('clear');

일단 number-0 button에만 이벤트 리스너를 달아 줬다.
button을 클릭하면 콜백함수의 매개변수로 받아온 event로 부터 이벤트 발생 대상을 알 수 있다.
(event 매개변수는 event 발생 대상의 정보를 담고 있다.)
event.target.textContent를 통해 눌린 버튼의 textContent 값을 받아와
$result.value에 표시한다.

이 방법이 간결하고 편하지만 공부를 위해 다른 방법을 사용해 볼 것이다.

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

document.querySelector('#number-0').addEventListener('click',(event) => {
  $result.value = '0';
});
document.querySelector('#number-1').addEventListener('click',(event) => {
  $result.value = '1';
});
document.querySelector('#number-2').addEventListener('click',(event) => {
  $result.value = '2';
});
document.querySelector('#number-3').addEventListener('click',(event) => {
  $result.value = '3';
});
document.querySelector('#number-4').addEventListener('click',(event) => {
  $result.value = '4';
});
document.querySelector('#number-5').addEventListener('click',(event) => {
  $result.value = '5';
});
document.querySelector('#number-6').addEventListener('click',(event) => {
  $result.value = '6';
});
document.querySelector('#number-7').addEventListener('click',(event) => {
  $result.value = '7';
});
document.querySelector('#number-8').addEventListener('click',(event) => {
  $result.value = '8';
});
document.querySelector('#number-9').addEventListener('click',(event) => {
  $result.value = '9';
});
document.querySelector('#plus');
document.querySelector('#minus');
document.querySelector('#div');
document.querySelector('#mul');
document.querySelector('calc');
document.querySelector('clear');

그냥 봐도 반복되는 코드가 너무 많다.
하지만 각 버튼은 각각의 값을 가지고 있기 때문에 각자 버튼에 해당되는 숫자를 반환해야 한다.
이를 고차 함수를 통해 해결해 보자.

함수의 특성

함수가 함수를 반환함 : 고차 함수

const func = () => {
	return () => {
    	console.log('hi');
    };
};
  • 반환된 함수는 다른 변수에 저장할 수 있고 변수에 저장된 함수를 다시 호출할 수 있다.
const innerFunc = func();
innerFunc(); //hi
let twice = function(f,v){
	return f(f(v));
};

let f = function(v){
	return v + 3;
};

console.log(twice(f,5));

twice(f,5) 로 호출 된 함수를 천천히 분석해 보자

매개 변수로 f,5를 전달한다 f의 경우 function(v)를 담고 있는 변수이다.

twice 함수는 return 으로 f(f(v))를 반환한다

  1. f(f(v)) => f(f(5))
    -> f 함수로 인해 5 + 3 연산 반환 // 8 반환
  2. f(8)
    -> f 함수로 인해 8 + 3 연산 반환 // 11 반환

반환하는 값을 바꾸고 싶을 때는 매개변수 사용
hello 문자열을 msg 매개 변수로 바꿀 수 있다.

const func = (msg) => {
	return () => {
    	console.log(msg);
    }
}

형태가 많이 간소화 되어 있다고 당황하지 말자
앞서 우리가 배운 형태이다
변수 대입법으로 풀어보면

const func = function(msg){
	return function(){
    	consol.log(msg);
    }
}

위와 같은 형태이다.

func 함수를 호출하면 func 함수에 넣은 매개변수를 console.log 하는 함수가 반환된다.

const innerFunc1 = func('hello');
const innerFunc2 = func('javaScript');
const innerFunc3 = func();
innerFunc1(); // 'hello'
innerFunc2(); // 'javaScript'
innerFunc3(); // undefined

func 함수 호출 부분을 return 값으로 대체하여 보자
이때 매개변수 위치에는 실제 값을 넣어야 함

const innerFunc1 = () => {
	console.log('hello');
};
const innerFunc2 = () => {
	console.log('javaScript');
};

const innerFunc3 = () => {
	console.log();
};

이렇게 func 처럼 함수를 만드는 함수를 고차 함수라 함
화살표 함수 문법에 따라 함수의 본문에서 바로 반환되는 값이 있으면 {} 와 return을 생략할 수 있음

const func = function(msg){
	return function(){
    	console.log(msg);
    }
}

const func = (msg) => {
	return () => {
    	cosole.log(msg);
    }
} 

const func = (msg) => () => {
	console.log(msg);
};

위 함수는 모두 같은 코드이다.

이 고차 함수를 통해 중복을 제거 해보자.

const $result = document.querySelector('#result');
const $operator = document.querySelector('#operator');
      
const clickBtn = (num) => () => {
  $result.value = num;
};

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

물론 이벤트 리스너를 다는 부분에 중복들이 눈에 보이지만 나중에 querySelectAll 의 내용을 자세히 다룰때 코드의 중복을 줄이는 방법을 설명하겠다.

바로 위 코드 보다 event를 이용한 코드가 더 간결하다. 고차 함수에 대한 이해를 위해 사용해 보았다.

그럼 고차함수를 이용하여 operator 버튼 들에도 이벤트 리스너를 달아보자

const clickOp = (op) => () => {
  $operator.value = op;
}

document.querySelector('#plus').addEventListener('click',clickOp('+'));
document.querySelector('#minus').addEventListener('click',clickOp('-'));
document.querySelector('#div').addEventListener('click',clickOp('/'));
document.querySelector('#mul').addEventListener('click',clickOp('*'));

이제 문제 해결을 해보자

첫째로 숫자버튼을 누를때 마다 새로운 숫자로 갱신된다.
즉 2자리 이상 숫자가 입력되지 않는다.

문제 해결법) 문자열 이용하기
: 문자열의 특성은 +연산을 통해 문자열을 연결할 수 있다.

let num1 = '';

const clickBtn = (event) => {
  num1 += event.target.textContent;
  $result.value = num1;
};

위와 같이 작성하면 문자열의 특성을 이용하여 여러 자리 수를 입력할 수 있다.

이제 연산자를 누르고 계산을 해보자
이때 또 문제가 발생 된다.
연산 자를 누른 후 result 창이 clear 되고 새로운 숫자가 입력 되야 하나 계속 연결 되어 입력 된다.
그럼 num2 변수를 입력하여 해결해 보자

      let num1 = '';
      let num2 = '';

      const clickBtn = (event) => {
        num1 += event.target.textContent;
        $result.value = num1;

        num2 += event.target.textContent;
        $result.value = num2;
      };

이때 우리는 if 문이 필요하다는 사실을 깨닫는다
천천히 문제를 생각해보자

계산기의 경우 num1이 먼저 입력되고 연산자가 입력된다.
그리고 연산자가 입력된 뒤 num2가 입력된다.

이말을 논리적으로 따져본다면
num1이 입력 되기전 연산자가 입력된면 num1이 입력이 안되었다는 것을 알려야 한다는 것이다.

또 num1이 있는 상태에서 연산자를 누르면 result를 clear하고 num2를 표시해야 한다는 것이다
이를 코드로 옮겨보자

let operator ='';

const clickBtn = (event) => {
  if(!operator){
    num1 += event.target.textContent;
    $result.value = num1;
  }else{
    if(!num2){
      $result.value = '';
    }
    num2 += event.target.textContent;
    $result.value = num2;
  }
};

const clickOp = (op) => () => {
  operator = op;
  $operator.value = operator;
}

operator 가 nudefined 라면 false를 반환한다 이를 반전시켜 oprator가 입력되어 있지 않을경우 num1이 입력 되게 만들고 아니라면 num2를 입력하게 만든다
operator가 입력된 상태에서 num2가 입력될때! 즉 undefined가 아닐때 $result.value를 ''로 만들고 num2를 표시해 줌으로 문제를 해결 할 수 있다.

분기를 만들때는 어떤 분기점의 절차가 더 짧은지 확인하여 짧은 절차를 먼저 작성해 준다.

위 코드를 간소화 해보자

const clickBtn = (event) => {
  if(!operator){
    num1 += event.target.textContent;
    $result.value = num1;
    return;
  }
  
  if(!num2){
    $result.value = '';
  }
  
  num2 += event.target.textContent;
  $result.value = num2;

};

if(!operator) 일 경우 처리를 완료하면 return; 을 사용하여 envent를 빠져나오게 만들었다.
return의 아래 코드들은 무조건 opration 일때만 동작하기 때문에 else문을 사용할 필요가 없어진다.

이제 연산결과를 출력해보자

document.querySelector('#calc').addEventListener('click',()=>{
  if(num2){
    switch(operator){
      case '+': 
        $result.value = parseInt(num1) + parseInt(num2);\
        break;
      case '-': 
        $result.value = num1 - num2;
        break;
      case '/': 
        $result.value = num1 / num2;
        break;
      case '*': 
        $result.value = num1 * num2;
        break;
      default:
        break;
    }
  }else{
    alert('숫자를 먼저 입력하세요.')
  }
});

일단 계산 결과를 출력하기 위해서는 num1, oprator, num2가 모두 입력 되어 있어야 한다.
하지만 앞에서 num2가 입력 되기 위한 조건으로 oprator가 있는지 판단했고, num1은 oprator가 없을때 입력된다
즉 num2만 확인하면 모든 확인이 된다는 뜻이다
num2가 입력되어 있지 않으면 숫자를 먼저 입력하라고 알려준다.

num2가 입력 되었을 경우 switch 문을 활용해 해당 연산의 결과를 $result.value에 표시한다.

이제 clear를 완성 해 보자

document.querySelector('#clear').addEventListener('click',()=>{
  num1 = '';
  num2 = '';
  operator = '';
  $result.value = '';
  $operator.value = '';
});

clear는 비교적 간단하다.

버그 고쳐보기

일반적으로 계산기는 한번으로 계산이 끝나고 clear를 누르지 않는다면 누적으로 계산이 가능하다.
하지만 현재 상태로는 num2가 지속 입력되어 연산 지속연산을 할 수 없다 이 버그를 수정해 보자.

const clickOp = (op) => () => {
  if(num2){
    switch(operator){
      case '+': 
        num1 = parseInt(num1) + parseInt(num2);
        break;
      case '-': 
        num1 = num1 - num2;
        	break;
      case '/': 
        num1 = num1 / num2;
        	break;
      case '*': 
        num1 = num1 * num2;
        	break;
      default:
        break;
    }
  }
  $result.value = num1;
  num2 ='';
  operator = op;
  $operator.value = operator;
}

operator가 눌릴때 새로 누른 oprator의 값으로 변경하기 전 이전 oprator의 값으로 연산을 한후 num1의 값으로 넣어주고 num2의 값을 비워 주면 해당 버그를 잡을 수 있다.

이외의 많은 버그가 있겠지만 공부 목적이므로 이정도만 하겠다.

0개의 댓글