본문 바로가기
기록/부트캠프

Javascript 계산기 프로젝트

by 디스코비스킷 2025. 3. 27.
반응형

자바스크립트 마지막 과제, 계산기 만들기

부트캠프 두번째 커리큘럼,
3주간의 자바스크립트 과목이 끝났다.

자바스크립트를 배운지는 꽤 됐지만,
다시 강의를 들으며 내가 헷갈렸던 것을 재점검하는 시간이 되었다.

 

다끝나가는 시점이지만, 모던자바스크립트딥다이브 스터디도 들어서
다시 한번 자바스크립트에 딥다이브 할 예정이다!

 

그래서,
막바지가 된 저번주 금요일에 최종과제인 계산기만들기에 대한 설명회가 있었다.
지난 기수의 최종결과물과 진행방식을 설명해주셨는데,
구현은 개인이지만 팀단위로 진행이된다는걸 알았고..

팀장이되어 조를 이끌게되었다. 

계산기 작동방식

먼저 계산기 구현을 위해
각 단계별로 상세한 요구사항이 있어서 아예 백지부터 혼자 하는건 아니었고,
지시에 따라 힌트를 얻으며 작성해나갔다.

 

계산기 작동방식은 이러하다.

  1. 숫자 버튼을 누르면 디스플레이에 숫자가 표시되고, 연산자 버튼을 누르면 현재 숫자를 firstOperand로 저장하고 연산 기호를 기억함.
  2. 연산자 클릭 후 새로운 숫자를 입력하면 디스플레이가 초기화되어 새로운 숫자를 입력할 수 있음.
  3. = 버튼을 누르면 계산 결과를 표시하고, 그 계산값을 가지고 연속 계산도 가능함.

최종 구현 화면

계산기 구현

하나에 이벤트리스너 등록 vs 개별로 이벤트리스너 등록

모든 버튼에 한번 이벤트로 등록하는것(하나의 forEach로 모든 버튼을 처리)

각 버튼에 개별적으로 이벤트 등록의 시간복잡도는 O(n)으로 같다.

버튼수에 비례해서 리스너가 등록된다. 

 

하지만 이벤트 처리시간에서 차이가난다. 

조건문이 많을 수록 조건을 체크하는 시간이 비례적으로 증가한다. 

특히 if 조건이 많거나, 여러번의 classList.contains()를 사용할 경우 조건 검사가 많이 늘어난다. 

 

여러번의 비교연산이 성능에 영향을 미칠 수 있지만,

소규모 프로젝트와 일반적인 사용범위에서는 성능의 차이는  미미하다. 

 

다만, 버튼의 수가 엄청 많거나 하나의 이벤트 리스너에 많은 조건문을 넣는 방식은 피하는게 좋다. 

이경우 조건문을 최적화하거나 이벤트 위임을 고려하는게 좋다고한다. 

 

성능의 차이는 미미하다하고 하지만 편한방법보다는

많은 조건문 작성을 피하기위해서 기능별로 분리를 해서 등록을 해줬다. 

 

innerText vs textContent

innerText브라우저 렌더링 후 화면에 표시된 텍스트를 반환한다. 그래서 CSS로 보이지 않게처리(display:none or visibility: hidden)된 텍스트는 포함되지 않는다. 성능에서 약간 느릴 수 있다. (reflow와 관련된 작업이 있을 수 있기 때문. reflow: 레이아웃 다시그리기)

 

textContentDOM요소 내의 모든 텍스트를 반환한다. 스타일과 관계없이 모든 텍스트가 포함된다. (심지어 공백마저도.. )

성능이 더 빠르다. 텍스트를 단순히 가져오는 작업이기 때문에 빠르고 효율적이다.

 

그래서 일반적인 경우 텍스트를 가져오는데는 textContent를 사용하는것이 더 효율적이다. 

보이는 텍스트만 필요할때는 innerText를 사용한다. 

DOM엘리먼트 선택, 변수선언

const displayNumber = document.querySelector(".display");
const operators = document.querySelectorAll(".operator");
const functions = document.querySelectorAll(".function");
const numbers = document.querySelectorAll(".number");
const equal = document.querySelector(".equal");
const clear = document.querySelector(".clear");
const decimal = document.querySelector(".decimal");
let firstOperand = null; // 첫번째 피연산자
let operator = null; // 연산자
let isSecondStart = true; // 플래그변수(두번째 피연산자 입력전인지)

isSecondStart는 계산 후에 연속 계산을 하기위해 만든 플래그변수다.
사실 다 만들고나니깐 그냥 secondOperand 선언하고 그게 null인지를 체크하면 될 것 같다.

숫자버튼을 클릭했을때

numbers.forEach((item) => {
item.addEventListener("click", () => {
const itemCont = item.textContent;
// 연산기호 버튼이 클릭된 후 두 번째 숫자를 입력하면 디스플레이의 값이 새로 입력한 숫자로 바뀝니다.
if (operator && isSecondStart) {
displayNumber.textContent = itemCont;
isSecondStart = false;
} else {
if (displayNumber.textContent === "0") {
displayNumber.textContent = itemCont;
} else {
displayNumber.textContent += itemCont;
}
}
if (displayNumber.textContent.length > 8) {
displayNumber.style.fontSize = "1.5rem";
}
});
});

number 클래스를 가진 모든 DOM엘리먼트들을 선택해 forEach로 각 아이템을 순회하며
그 item에 클릭이벤트를 등록한다.

 

operator && isSecondStart는 둘다 true일때 만족하는 조건으로
첫계산 후 두번째 피연산자를 받아 화면에 새숫자를 보여주기위해 작성하였다.

 

그게 하나라도 false라면
다시 디스플레이숫자가 0인지 확인하고

  • 0인 경우: 클릭된숫자를 디스플레이숫자에 넣기
  • 0이 아닌경우: 클릭된 숫자를 디스플레이숫자 마지막에 추가하기

 

그리고 꼭 필요하진 않지만
숫자가 8자가 넘어갈 경우(내화면에서 8자까지만 나왔음) 폰트크기를 줄여줬다.
(css는 overflow: hidden)

 

오늘 강사님 실시간세션 풀이를 보고는
else문이 많은데 현업에서는 else를 거의 쓰지않고 얼리리턴을 많이 쓴다고 하던데
그 방법도 리팩토링할때 고려해봐야 될 것 같다.

연산자 버튼을 클릭했을때

operators.forEach((item) => {
item.addEventListener("click", () => {
if (firstOperand && operator && !isSecondStart) {
calculate(Number(firstOperand), operator, Number(displayNumber.textContent));
return;
}
firstOperand = displayNumber.textContent;
operator = item.textContent;
console.log(`First Operand: ${firstOperand}, Operator: ${operator}`);
});
});

firstOperand && operator && !isSecondStart
첫번째피연산자와 연산자가 있고, 두번째연산자를 입력받았는지를 묻는 조건인데

 

처음 변수선언때 언급했듯.. secondOperand가 null이 아닌지
firstOperand && operator && secondOperand 인지로 수정하면 좋을것 같다.

 

그래서 이게 들어가는 부분은
8 * 5 = * 4 * 👈🏻 요 마지막 연산자를 눌렀을때 만족되어 바로 calculate 돼야한다.
각 인자를 넘겨줄때 피연산자들은 string이므로 number로 형변환을 해서 넘긴다.

calculate() 함수

const calculate = (firstOperand, operator, secondOperand) => {
let result = 0;
switch (operator) {
case "+":
result = firstOperand + secondOperand;
break;
case "-":
result = firstOperand - secondOperand;
break;
case "*":
result = firstOperand * secondOperand;
break;
case "/":
result = firstOperand / secondOperand;
break;
default:
break;
}
displayNumber.textContent = result;
operator = null;
firstOperand = result;
isSecondStart = true;
};

switch문으로 각 case와 operator가 일치하면 실행되는데
각 연산자로 연산을 해서 result에 담는다.

 

그리고 계산 했으니까,
operator와 isSecondStart는 처음값으로 되돌려주고,
firstOperand에 result값을 넣는다.

=, C, . 버튼을 클릭했을때

equal.addEventListener("click", () => {
calculate(Number(firstOperand), operator, Number(displayNumber.textContent));
});
clear.addEventListener("click", () => {
displayNumber.textContent = "0";
firstOperand = null;
operator = null;
isSecondStart = true;
displayNumber.style.fontSize = "3rem";
});
decimal.addEventListener("click", () => {
if (!displayNumber.textContent.includes(".")) {
displayNumber.textContent += ".";
}
});

=를 눌렀을때는
클릭이벤트가 발생했을때 calculate함수를 실행하도록 등록했고,

 

C를 눌렀을때는 변경해주었던 모든것을 처음값으로 되돌려주도록 했다.

 

그리고 .를 눌렀을땐,
디스플레이숫자에 .이 없으면 뒤에 추가하도록했다.

추가적으로 수정하면 좋을것들

  • %와 ± 버튼 기능 구현이 빠져서 더 하면 좋겠다.
  • DOM 요소에 직접 접근해서 매번 바꾸지 않고, 자바스크립트와 분리해서 내부적으로 값을 다 처리하고 디스플레이할때 한번에 처리하면 좋을것같다.
  • 클릭이벤트에 줬던 함수를 깔끔하게, 함수를 다 나눠서 작성하고 리턴값이 나오도록...
    같은 말이지만 calculate함수 내부에서 display업데이트, 상태변경 분리하기. (책임을 나누면 테스트와 재사용성이 쉬워진다)

다음엔 이 코드를 리팩토링 해볼것이다.

 

팀장 수행 소감

팀원은 3명이었는데,

각 진행단계에 따라 조교님 컨펌전에 먼저 코드를 확인하고 피드백을 주고

미팅잡고, 자료 전달하기만 하면 되는 정~말 간단한 일이었다.

 

그렇게 책임이 막중한건 아니었다.

다만 누군가를 이끄는일을 대학교 졸업작품전시회에서 XX부장을 맡은 이후로

거의 처음인것 같아서 얼떨떨하고 느낌이 생소했다.

 

하나라도 도움되게 개념들같은걸 정리해서 주기도 하고,

피드백을 줄 때는 팀원들이 혹시라도 기분이 상하지 않게 조심스럽게 진행했다. 

 

이 최종과제가 팀미션이다보니 나만 혼자 빨리 하면되는게 아니고,

내 과제를 한 시간보다는 팀원 과제를 신경쓴시간이 더 길었던것같다. 

 

간단한 과제지만 현업도 그러할것이다.. 

회사다닐때 팀장님의 마음을 조금이라도 알것같았다... ㅎ  

 

다음에도 혹시라도, 팀장이 또 된다면.... ?

잘한다는 보장은 못하겠지만,

그땐 규모가 더 커지니 머리도 터지겠지만,

더욱 세밀하게 신경쓰며 관리해내서, 나와 모두가 더 성장할 수 있도록 하고싶다. 

반응형

최근댓글

최근글

© Copyright 2024 ttutta