<22.08.25> 바닐라 자바스크립트로 계산기를 만들어보자
국비학원에서 HTML/CSS/JS를 이용하여 웹 페이지를 만드는 과제를 하다가 시간이 남아 바닐라 JS로 계산기를 만드는 개인 과제를 진행해보았다. 더하고 빼고 곱하고 나누기만 가능한 최소한의 기능만 가진 계산기를 만드려고 했다. 기능이 얼마 없으니 그렇게 까다롭지 않을 것이라 생각했는데 생각보다 어려웠다. 오늘은 바닐라 자바스크립트로 계산기를 만들기 위해 구상했던 것과, 실제 JS 코드를 소개하고자 한다.
1. 구상
HTML/CSS로 계산기 UI를 구성하고, JS를 통해 핵심적인 기능을 구현한다. 계산기에 들어갈 기능은 아래와 같다.
- 사칙연산(덧셈, 뺄셈, 곱셈, 나눗셈) + 몫 계산(JS에서의 % 연산)
- 화면창 초기화
- 삭제버튼
- 숫자패드
- 출력창(최종결과출력화면 / 입력현황출력화면)
계산기 기능 구현을 위해 JS object를 활용하였다. 그리고 패드 입력에 따른 이벤트를 통해 생성한 object를 조작하여 연산을 처리하는 것으로 구현하고자 하였다.
2. UI 구현 및 디자인
HTML을 통해 계산기 화면과 숫자/기능패드들을 구현하고, CSS를 통해 UI를 꾸며주었다. 아래는 HTML 코드이다.
<body>
<section class="calc">
<h1>계산기</h1>
<div class="box">
<div class="input_window">
<div class="result"></div>
<div class="current"></div>
</div>
<div class="btn_box">
<div class="btn operand">+</div>
<div class="btn operand">-</div>
<div class="btn operand">X</div>
<div class="btn func">DELETE</div>
<div class="btn number">1</div>
<div class="btn number">2</div>
<div class="btn number">3</div>
<div class="btn operand">/</div>
<div class="btn number">4</div>
<div class="btn number">5</div>
<div class="btn number">6</div>
<div class="btn operand">%</div>
<div class="btn number">7</div>
<div class="btn number">8</div>
<div class="btn number">9</div>
<div class="btn operand">=</div>
<div class="btn number">0</div>
<div class="btn func">CLEAR</div>
</div>
</div>
</section>
</body>
계산기를 감싸는 전체 div에 .box를 부여하고, 그 아래에는 출력화면창들을 감싸는 .input_window 박스와 숫자/기능들을 클릭할 수 있는 패드들을 감싸는 .btn_box가 존재한다.
.input_window의 .result 박스에는 최종 연산 결과를 보여주며, .current는 숫자패드와 연산자패드 클릭을 통해 현재까지 작성한 식을 출력해준다.
.btn_box에 있는 .btn은 실제로 클릭할 수 있는 버튼들이다. 버튼의 유형을 세 가지로 나누어 그 기능에 따라 각기 다른 클래스를 부여하였다. 그 기능과 클래스는 아래와 같다.
기능 | 역할 | 클래스 |
숫자 입력 | 피연산자를 입력할 수 있다 | .number |
연산자 입력 | 연산자(+,-,*,/,%)를 입력할 수 있다 | .operand |
기능 | 계산을 도와주는 부가기능 | .func |
아래는 CSS 코드이다.
.calc {
margin: 0 auto;
width: 800px;
text-align: center;
}
.calc h1 {
margin-bottom: 30px;
padding: 35px 0;
font-size: 35px;
font-weight: 700;
}
.calc .box {
display: grid;
grid-gap: 5px;
padding: 10px;
}
.calc .box .input_window {
outline: 1px solid #ddd;
columns: 1/5;
margin-bottom: 20px;
border-radius: 50px;
padding: 10px 25px;
height: 100px;
}
.calc .input_window .result {
border-bottom: 1px solid #ddd;
height: 70%;
}
.calc .input_window .current {
height: 30%;
}
.calc .box .btn_box {
columns: 1/5;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-columns: 200px;
grid-gap: 5px;
padding: 5px;
}
.calc .btn_box .btn {
outline: 1px solid #ddd;
border-radius: 15px;
height: 30px;
text-align: center;
line-height: 30px;
cursor: pointer;
}
.calc .btn_box .operand,
.calc .btn_box .func {
color: #fff;
background-color: #f00;
}
.calc .btn_box .func:last-child {
grid-column: 4 / span 1;
}
HTML과 위 CSS 코드를 통해 출력된 화면은 아래와 같다
출력창의 border를 기준으로 상단 화면은 연산결과가, 하단의 화면은 사용자가 입력한 숫자, 연산자를 포함한 식이 출력된다. 숫자 패드는 0~9까지로 하얀색 배경으로, 그리고 연산자와 부가 기능 버튼은 빨간색 배경으로 처리하였다.
3. JS 코드
3-1. UI 기능을 위한 코드
UI 구현 및 디자인까지 완료하였으므로, JS를 통해 기능을 구현할 차례이다. 계산기 기능 구현을 위한 코드는 크게 두 가지로 구성하였는데, 하나는 계산기 기능을 위한 object 코드와, 계산기 패드 입력에 따른 이벤트 처리를 위한 코드가 있다. 계산기 패드를 클릭했을 때 발생하는 'click' 이벤트에 미리 구현한 계산기 객체의 특정 메소드를 호출하는 방식으로 코드를 구성하였다.
아래 코드는 계산기 패드의 다양한 유형에 따른 서로 다른 이벤트 처리를 위한 코드이다.
const BTN = document.querySelector('.btn_box');
BTN.addEventListener('click', function (event) {
const currentClick = event.target;
// 계산기 패드를 눌렀을 때
if (event.target != event.currentTarget) {
// 숫자 패드를 눌렀을 때
if (currentClick.classList.contains('number')) {
}// 연산자 패드를 눌렀을 때
else if (currentClick.classList.contains('operand')) {
// '=' 버튼을 눌렀을 때
if (currentClick.innerText == '=') {
}
} else // func 패드를 눌렀을 때 {
// DELETE 패드를 눌렀을 때
if (currentClick.innerText == 'DELETE') {
}
// CLEAR 패드를 눌렀을 때
else {
}
}
});
변수 BTN은 계산기 패드 HTML 요소를 담았다. 계산기 패드 영역을 클릭하고, addEventListener 메소드의 콜백 함수에서 event.target을 통해 사용자가 실질적으로 클릭한 버튼을 인식하도록 만들었다.
사용자가 클릭한 버튼의 클래스에 따라 연산 처리를 다르게 하기 위해 조건분기문 if문을 사용하였다. 지금 생각하면 switch로 작성하는 것이 더욱 적절했겠다는 생각이 든다. 다음에 기회가 된다면 위 코드를 switch문으로 변경해보겠다.
처음으로 등장하는 if문 ( event.target != event.currentTarget )의 경우, 계산기 패드의 영역은 아니지만, HTML 코드 상으로 .btn_box 내부의 영역을 클릭할 경우, 코드가 실행되는 경우를 방지하기 위함이다.
보라색으로 마킹한 영역은 계산기 패드가 아니지만, 위 영역을 클릭할 경우 콜백함수가 실행되어 버그가 발생한다. 이를 방지 하기 위해 사용자가 실질적으로 클릭한 요소를 반환하는 e.target과 이벤트가 부착된 요소만 반환하는 e.currentTarget가 동일할 때에만 코드가 실행될 수 있도록 처리를 해준 것이다
위 코드는 패드 클릭에 따른 연산을 처리하기 위한 껍데기같은 역할을 한다. 이제는 본격적으로 계산을 수행하는 코드를 생성하여, 위에서 작성한 이벤트 처리 코드와 연동하면 계산기 구현이 마무리 된다.
3-2. 계산 기능을 위한 코드
계산 기능 구현을 위해서 javascript의 object를 활용하였다. HTML/CSS로 구현한 계산기를 적절히 구현하기 위해 필수적으로 구현해야 할 전체적인 기능들이 있었다. 이는 아래와 같다.
- 패드 클릭 시 클릭한 숫자 or 연산자를 화면에 출력시키는 기능
- 사용자가 입력한 수식을 최종적으로 계산하는 기능
- CLEAR 버튼 클릭 시 출력창 내용 및 진행 사항을 초기화하는 기능
- DELETE 버튼 클릭 시 출력창 내용 및 진행 사항을 하나씩 삭제하는 기능
크게는 네 가지의 기능을 JS코드로 구현해야 했다. 하지만 실제로 코드를 작성하면서, 위 사항들을 기본으로 세부적인 부분까지 신경써야 했는데, 이런 세세한 기능 구현들이 생각보다 까다로웠음을 느꼈다. 지금부터는 계산기 구현을 위해 선정했던 네 가지의 기능을 각 섹션으로 나누어 상세히 기술하도록 하겠다.
3-2-0. 계산기 객체 초기화
기능 구현에 앞서 object로 생성한 계산기 객체의 기본 property에 대해 설명하고자 한다. 계산기 객체의 property는 아래 JS 코드와 같다
function CALC_OBJ() {
this.name = 'calc';
this.window_acc = '';
this.result = 0;
this.operand_flag = false;
this.expression = new Array();
}
생성자 함수를 통해 CALC_OBJ라는 이름으로 자바스크립트 객체를 생성하였다. 이제부터는 'var calc = new CALC_OBJ()와 같은 형식으로 계산기 object를 생성할 수 있다. 위 생성자 함수에 들어간 property(프로퍼티이면서 변수)들은 아래와 같은 기능을 한다
변수명 | 역할 |
name | 객체의 기본 이름 |
window_acc | 패드 클릭 시 화면에 출력시킬 값 |
result | 최종 연산 결과 |
operand_flag | 연산자 패드 클릭 여부 |
expression | 사용자가 입력한 수식을 저장해두는 배열 |
생성자 함수 생성 시, 계산 기능을 위해 작성한 일부 로직들 중 접두사로 __를 작성한 함수들이 존재한다. ( ex: this.__show() ) 이는 object 내부에서만 사용하는 함수라는 의미이다. 이외에 접두사가 존재하지 않는 함수는, object 외부에서 직접 호출하여 사용하는 메소드란 의미이다.
굳이 이렇게 코드를 작성한 이유는, 함수 내부 로직이 길어질 경우 코드 길이가 길어지는 것을 방지하기 위해 외부 함수와 내부 함수를 구분하였다.
3-2-1. 화면 출력 기능 구현
사용자가 계산기를 사용할 때, 진행사항을 확인할 수 있도록 숫자, 연산자 패드를 클릭할 때마다 그 내용을 화면에 출력시킬 필요가 있다. 또한 CLEAR나 DELETE 패드를 눌렀을 때, 그에 맞게 수식이 수정되어야하며, 그 결과가 실시간으로 화면에 출력되어야한다. 아래는 이를 위한 코드이다.
function CALC_OBJ() {
...
this.__windowNumClick = function (value) {
this.window_acc += value;
};
this.clickShow = function (value, showObj) {
this.__windowNumClick(value);
showObj.innerText = this.window_acc;
};
}
숫자 패드와 연산자 패드를 클릭할 때마다 그 결과가 화면에 출력되어야 한다. this.clickShow 함수는 사용자가 클릭한 값(숫자 or 연산자)과 출력시킬 HTML 요소를 parameter로 받는다. 사용자가 클릭한 값은 this.__windowNumClick 이라는 내부 함수에서 처리된다.
this.__windowNumClick 함수는 사용자가 클릭한 값을 this.window_acc에 추가한다. 이 때, 매개변수로 받는 값의 type은 'string'이다. 화면에 출력을 시킬 때에는 굳이 숫자일 필요가 없기 때문에 number type으로 변경해주지는 않았다. this.__windowNumClick 함수를 통해 this.window_acc 변수가 변경되면, 이를 곧바로 HTML 요소에 출력시킨다.
3-2-2. 계산 기능 구현
본격적인 계산기 기능 구현을 위한 코드를 소개하는 섹션이다. 사용자가 입력한 숫자와 연산자를 바탕으로 수식을 계산하고 그 결과를 출력하는 로직을 소개하고자 한다.
코드 소개에 앞서, 내가 구현한 계산기의 작동 원리를 개략적으로 설명하겠다. 우선 사용자가 클릭한 숫자와 연산자를 순차적으로 배열에 저장한다. 그 뒤에 사용자가 연산자 버튼 중 '=' 버튼을 클릭할 경우 저장해두었던 배열의 원소에 접근하여 사용자가 입력한 수식을 계산해낸다. 그리고 계산 결과를 화면창에 출력시킨다.
원래는 사용자가 패드를 입력할 때마다 실시간으로 계산을 진행하는 방식으로 코드를 구성하고 싶었으나 잘 되지 못해 위와 같은 방식으로 구성하게 되었다. 실시간으로 계산을 진행하려면, 연산자 패드를 누를 때마다 이전에 클릭하였던 숫자에 사용자가 입력하였던 연산자에 맞게 연산을 진행해야 한다. 수식은 항상 숫자부터 입력하고 난 뒤 연산자를 입력하게 되는데, 이에 따라 가장 처음으로 연산자 패드를 클릭할 때에는 어떤 처리를 해주어야 할 지 적절한 방법을 찾지 못했다.
이에 따라 사용자가 클릭한 값들을 순차적으로 배열에 저장시켜, 마지막에 배열을 순회하여 계산을 진행하는 방식으로 바꾸었다. 아래 코드는 계산 기능 구현을 위한 코드이다.
function CALC_OBJ() {
...
this.numClick = function (value) {
this.operand_flag = false;
this.__expressionAdd(value);
};
this.operandClick = function (value) {
if (!(this.operand_flag)) {
this.operand_flag = true;
this.__expressionAdd(value);
}
};
this.resultClick = function (showObj) {
this.__calculate();
showObj.innerText = this.result;
};
}
계산 기능을 위한 코드는 조금 길어서 내부 로직과 외부 로직을 나누어서 소개하겠다. 우선 위 코드는 외부에서 선언된 계산기 object의 메소드를 호출하여 사용하는 외부 함수이다.
this.numClick 함수는 사용자가 숫자 패드를 클릭하였을 때 실행되는 함수이다. 이 함수는 사용자가 클릭한 값 중 숫자 값만 받는다. 사용자가 숫자 패드를 클릭할 때마다, this.operand_flag를 false로 만들어 연산자 패드를 누르지 않았음을 객체에 알려준다. 이 변수에 대한 설명은 차후 설명하겠다. 그리고 this.__expressionAdd라는 내부 함수에 사용자가 클릭한 값을 전달한다. 이 내부 함수는 객체 생성 시 초기화한 배열에 추가하는 역할을 한다.
this.operandClick 함수는 사용자가 연산 패드를 클릭하였을 때 실행되는 함수이다. 해당 함수가 호출되었을 때, this.operand_flag가 false인지 확인하고, false라면 이를 다시 true로 변경한 뒤 연산자를 this.__expressionAdd 함수를 통해 배열에 추가한다.
this.operand_flag의 역할은, 사용자가 연산자를 중복으로 입력했을 때, 연산 결과에 영향을 끼치지 않게 하기 위함이다. 보통은 연산자를 누른 뒤 다시 숫자를 클릭할 테니, 숫자 패드를 클릭하여 numClick 함수 실행 시 다시 operand_flag를 false로 만들어 다음 번에 연산자를 누를 차례가 되었을 때 정상적으로 연산자를 추가할 수 있게 한다. 만일 operand_flag가 없다면, 연산자를 연속적으로 누르면 그 횟수만큼 배열에 추가되어 연산시 정상적으로 결과가 출력되지 않을 것이다.
this.resultClick 함수는 연산 결과를 출력시킬 HTML 요소를 인자로 받아, 내부 함수 this.__calculate()의 결과를 통해 반환된 연산 결과값을 출력시켜준다.
아래는 내부 함수들이다.
function CALC_OBJ() {
...
this.__expressionAdd = function (value) {
if (!(this.operand_flag)) {
this.expression.length ?
this.expression[this.expression.length - 1] += value :
this.expression.push(value);
} else {
if (value == '=') {
this.expression.push(value);
} else {
this.expression.push(value);
this.expression.push('');
}
}
this.__calculate = function () {
this.expression.forEach(function (elm, idx, arr) {
if (typeof (parseInt(elm)) == 'number') {
if (!(idx)) {
this.result += parseInt(elm);
} else {
if (arr[idx - 1] == '+') {
this.result += parseInt(elm);
} else if (arr[idx - 1] == '-') {
this.result -= parseInt(elm);
} else if (arr[idx - 1] == 'X') {
this.result *= parseInt(elm);
} else if (arr[idx - 1] == '/') {
this.result /= parseInt(elm);
} else if (arr[idx - 1] == '%') {
this.result %= parseInt(elm);
}
}
}
}, this)
};
};
외부 함수였던 this.numClick 함수와 this.operandClick에서 사용되었던 this.__expressionAdd 함수는 사용자가 클릭한 패드의 값을 this.expression이라는 배열에 추가해주는 함수이다. this.expression은 앞서 기술하였듯이, 최종적으로 연산이 진행될 값들이 저장된 배열이다.
이 함수가 실행되면, 입력으로 받은 값이 연산자인지, 숫자인지 확인한다. 숫자라면, this.expression 배열의 길이를 확인하여 비어있을 시, 곧바로 입력값을 배열에 추가하고, 비어있지 않다면 배열의 마지막 원소값에 숫자를 더해준다. 이 때 더한다는 것은 정수 간의 합이 아닌, 문자열의 합이다.
입력받은 값이 연산자일 경우, 그 값이 '='인지 아닌지 검사한다. 만약 '='일 경우 등식기호만을 추가하고 함수를 종료하고, 사칙연산 기호일 경우 연산자를 this.expression 배열에 추가한 뒤 빈 문자열을 별도로 추가한다. 빈 문자열을 추가하는 이유는, 사용자가 연산자 입력 이후 다른 숫자를 입력하였을 때 this.expression 배열의 가장 마지막에 위치한 연산자에 사용자가 클릭한 숫자가 더해지는 것을 방지하기 위함이다.
만약 사용자가 35+ 까지 입력한 상황이라고 가정하자, 그렇다면 this.expression은 ['35','+']와 같은 형태로 저장이 되어있을 것인데, 만약 이 상태에서 사용자가 6이라는 숫자패드를 클릭할 경우, 위 로직에 따라 가장 마지막 원소에 곧바로 문자열 합을 진행하게 된다 ( this.expression[this.expression.length - 1] += value ) 이 경우, this.expression은 ['35','+6']이 된다. 이러한 상황을 방지하기 위해 '='가 아닌 사칙연산 기호를 입력하였을 때 빈 문자열을 삽입하여 this.expression을 ['35'+'']과 같은 상황을 만들어 연산자 패드 클릭 이후 숫자 패드를 클릭했을 때에도 this.expression에 연산자와 구분되게 숫자를 추가할 수 있다.
this.__calculate 함수는 사용자가 여태껏 클릭한 수식을 한꺼번에 계산하는 로직이다. 위 내부 함수는 사용자가 연산자 패드 '='를 클릭하였을 때 호출된다. 내부 함수인 this.__calculate가 실행될 경우 this.expression 배열을 순차적으로 순회한다. forEach 메소드를 활용하여 각 원소가 숫자인지 연산자인지 판단하여 연산을 진행한다.
원소가 숫자일 경우, 특히 가장 맨 처음 원소일 경우 this.result에 사용자가 최초로 입력한 숫자를 저장할 필요가 있으므로, 배열의 가장 맨 처음에만 이 코드가 실행될 수 있도록 처리한다. 이후 배열 원소에 순차적으로 접근하여 현재 인덱스의 이전값이 연산자일 경우 그 연산자에 맞는 연산을 진행하여 this.result에 그 값을 순차적으로 저장한다. 결과적으로 this.expression의 맨 마지막 원소인 '='에 도달했을 때 모든 연산이 종료되어 this.result에 그 결과값이 저장된다.
3-2-3. CLEAR 기능 구현을 위한 코드
사용자가 특정 수식에 대한 연산이 종료되었을 경우, 출력화면이나 진행사항을 모두 지우고 싶을 때가 있다. CLEAR는 이를 위한 기능이다. 사용자가 CLEAR 버튼을 눌렀을 때, 출력창에 모든 내용이 지워짐과 동시에, 계산기 객체가 저장하고 있던 사용자의 수식 진행상황 또한 초기화시킬 필요가 있다. 아래 코드는 이를 위한 함수이다.
function CALC_OBJ() {
...
this.clearClick = function (...clearObj) {
clearObj.forEach(function (elm) { elm.innerText = ''; });
this.window_acc = '';
this.expression = [];
this.result = 0;
};
clearClick 함수는 비우고 싶은 HTML 요소 여러개를 인자로 받는다. 실제 HTML에서 연산 결과를 출력하는 .result와 현재 수식의 진행상황을 알려주는 .current가 존재하니, 이를 한꺼번에 인자로 받아 각 요소의 텍스트값을 모두 지우는 것이 간편할 것이라 생각했다.
가변인자로 받은 인자들을 forEach 메소드를 통해 각 원소들의 텍스트값을 빈 값으로 변경하고, 계산기 객체에 계속해서 누적해두고 있었던 property들 또한 비어있는 문자열, 빈 배열, 0으로 초기화시켰다.
3-2-4. DELETE 기능 구현을 위한 코드
계산기를 사용하는 도중 숫자를 잘못 입력하거나 연산자를 잘못 입력하는 경우가 있다. 이 때, CLEAR를 눌러 처음부터 다시 작성할 수 있겠지만, 수식이 길 경우 CLEAR 기능은 번거로울 수 있다. 이에 따라 입력된 수식을 마지막부터 순차적으로 제거하는 기능이 필요하다. 우선 외부 함수부터 살펴보자.
function CALC_OBJ() {
...
this.deleteClick = function (delete_obj) {
let result = delete_obj.innerText.split('');
let temp = result.splice(-1, 1);
delete_obj.innerText = result.join('');
this.__windowNumDelete();
this.__expressionDelete();
this.result = 0;
};
deleteClick 함수는 지운 수식을 다시 출력하고자 하는 HTML 요소를 인자로 받는다. result라는 변수를 선언하고, HTML 요소에 출력된 수식을 모두 배열로 변환한 뒤, temp라는 임시변수를 선언한 뒤, 배열 result의 마지막 원소를 삭제한 결과값을 저장한다. 이후 result 배열에는 마지막 값이 삭제된 배열이 남게되는데, 이를 delete_obj의 innerText를 통해 재출력시킨다. 이 때 배열을 그대로 출력시키지 않고 .join()메소드를 활용해 하나의 완성된 수식을 출력시킨다.
이후 this.__windowNumDelete 함수와 this.__expressionDelete 함수를 호출한다. 아래 코드는 내부 함수들이다.
function CALC_OBJ() {
...
this.__windowNumDelete = function () {
let w_result = this.window_acc.split('');
let w_temp = w_result.splice(-1, 1);
this.window_acc = w_result.join('');
};
this.__expressionDelete = function () {
let lastAtom = this.expression[this.expression.length - 1];
if (lastAtom.length > 1) {
let temp = lastAtom.split('');
temp.pop();
this.expression.splice(-1, 1, temp.join(''));
} else {
this.expression.splice(-1, 1);
}
};
}
__windowNumDelete 함수의 경우 계산기 객체 내부에서 사용하기 위한 화면출력용 수식을 지우는 함수이다. 지금 생각해 보니 굳이 위 함수는 없어도 될 것 같다. this.__windowNumDelete 함수 내부에 HTML 요소를 받아 this.window_acc를 HTML 요소에 출력시켰으면 이중으로 코드를 작성하지 않아도 되었을텐데 말이다. 이 함수의 로직은 외부 함수 deleteClick과 동일하므로 자세한 설명은 생략하겠다.
this.__expressionDelete는, 사용자가 Delete 키를 눌렀을 때 여태까지 계산기 객체 내부에 수식을 저장해두었던 배열this.expression에도 delete 기능을 적용시키기 위한 내부함수이다. 개인적으로 이 코드를 작성했을 때가 가장 까다로웠다. 길이가 하나로 고정되어있는 연산자와 다르게, 길이가 천차만별인 숫자(배열에 저장된 type은 string이다)의 경우 끝에 위치한 숫자만을 삭제하고 이를 다시 배열 마지막에 추가하는 것이 JS초보자인 나에게는 꽤 어려운 작업이었다.
먼저 lastAtom이라는 변수를 선언하고 this.expression 배열의 마지막 원소를 삽입한다. lastAtom의 길이가 1을 초과할 경우, lastAtom을 분해한 뒤 임시 변수 temp에 저장한다. 이후 임시변수 temp에 .pop 함수를 통해 마지막 원소를 제거한다. 그리고 this.expression의 마지막 배열에 lastAtom의 마지막 원소가 삭제된 값을 삽입한다.
만일 lastAtom의 길이가 1일 경우, 연산자이거나 한 자릿수의 숫자이니 별도의 처리 없이 배열 this.expression의 마지막 값을 삭제한다.
4. 구현결과
아래는 실제 구현한 계산기를 테스트한 영상이다
5. 느낀점 및 개선점
구글링을 통해 JS로 계산기를 만든 다른 이의 블로그를 탐방해보았는데, eval을 사용하여 계산기를 구현한 경우가 많았다. 연산기능을 구현하면서, 마치 string을 number로 형변환하여 계산 가능한 숫자로 만들 수 있듯이, string으로 된 연산자도 실제 계산이 가능한 진짜 사칙연산 기호로 변환할 수 있으면 얼마나 좋을까 라고 생각하여 검색하였는데 eval 함수를 사용하면 실제 문자열 식을 JS 수식으로 변환이 가능함을 알았다. 하지만 조금 더 알아보니 여러 이유로 eval is evil이란 문구를 보고 eval을 사용하지 않고 직접 계산기능을 구현하고자 했다.
기능 구현 후 직접 계산기를 사용해보았는데, 아직 한계점이 많았다. 도중에 생각치 못한 오류도 있었고, 코드 상으로도 조금 더 축약이 가능한 부분도 있어 아쉬웠다. 우선 여태껏 계산기를 구현하면서 확인한 오류 및 아쉬운 점들을 아래와 같이 정리해보았다.
- 한자리 숫자를 삭제하고 다시 연산을 진행할 경우 제대로 연산이 되지 않다
- 356 x 32 를 입력하고 delete를 누른뒤 356 x 3을 실행할 경우 정상적으로 연산이 진행되나, 356 x 3을 입력하고 delete를 누르고 356 x 6과 같이 실행할 경우 연산이 진행되지 않는다
- 연산자 중복 입력 시 내부 로직에는 반영되지 않으나 결과창에 그대로 출력되어 보기 좋지 않다
- 36 x 까지 작성하고, 다른 연산자를 입력시 진행사항 출력창에 연산자가 추가적으로 입력된다. 36xxx-+ ..
- 연산의 우선순위를 정할 수 없다
- 덧셈, 뺄셈,곱셉,나눗셈이 한꺼번에 있을 경우 순차적으로 계산하여 잘못된 결과가 출력된다.
- 음수 계산이 불가능하다
- 계산기 입력 시 최초에는 연산자 입력이 반영되지 않도록 코드를 작성하였기에 음수 부호를 붙일 수 없다
- 코드 내부에 중복되는 로직이 존재한다
- 하나의 로직으로 통일할 수 있는 코드들이 존재한다. 정리 필요.
이 외에도 확인하지 못한 여러 오류들이 있을 수도 있지만, 현재까지 발견한 오류 및 아쉬운 점은 위와 같다. 또한 추가하고 싶은 기능도 있다.
- 괄호 기능 추가
- 소수점 계산 기능 추가
- 제곱근 연산 추가
향후 기회가 된다면 위에서 제시한 오류들을 개선하고, 추가하고 싶은 기능들을 구현하여 조금 더 나은 형태의 계산기를 구현하면 공부에 더욱 도움이 될 것 같다!
P. S 조잡한 글 읽어주셔서 감사합니다! 혹시나 틀린 정보가 있다면 과감한 지적(하지만 욕은 삼가해주시면 감사하겠습니다 ㅎㅎ) 부탁드립니다.