<!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))를 반환한다
반환하는 값을 바꾸고 싶을 때는 매개변수 사용
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의 값을 비워 주면 해당 버그를 잡을 수 있다.
이외의 많은 버그가 있겠지만 공부 목적이므로 이정도만 하겠다.