티스토리 뷰

문제풀이

계산기 기능 구현

네스사 2023. 2. 23. 19:06

오늘 페어님이랑 같이 구현한 계산기입니다. 일단 코드 전문부터

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JavaScript Calculator</title>
    <link
      rel="stylesheet"
    />
    <link rel="stylesheet" href="./yourStyle.css" />
  </head>
  <body>
    <div class="container">
      <div class="calculator">
        <div class="calculator__display--bare">
          <span class="calculator__operend--left">0</span>
          <span class="calculator__operator">+</span>
          <span class="calculator__operend--right">0</span>
          <span class="calculator__equal">=</span>
          <span class="calculator__result">0</span>
        </div>
        <div class="calculator__display--for-advanced">0</div>
        <div class="calculator__buttons">
          <div class="clear__and__enter">
            <button class="clear">AC</button>
            <button class="calculate">Enter</button>
          </div>
          <div class="button__row">
            <button class="number">7</button>
            <button class="number">8</button>
            <button class="number">9</button>
            <button class="operator">+</button>
          </div>
          <div class="button__row">
            <button class="number">4</button>
            <button class="number">5</button>
            <button class="number">6</button>
            <button class="operator">-</button>
          </div>
          <div class="button__row">
            <button class="number">1</button>
            <button class="number">2</button>
            <button class="number">3</button>
            <button class="operator">*</button>
          </div>
          <div class="button__row">
            <button class="number double">0</button>
            <button class="decimal">.</button>
            <button class="operator">/</button>
          </div>
        </div>
      </div>
      <img class="logo" src="data/codestates-logo.png" />
    </div>
    <script src="./script.js"></script>
  </body>
</html>

 

const calculator = document.querySelector(".calculator"); // calculator 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
const buttons = calculator.querySelector(".calculator__buttons"); // calculator__keys 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.

const firstOperend = document.querySelector(".calculator__operend--left"); // calculator__operend--left 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
const operator = document.querySelector(".calculator__operator"); // calculator__operator 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
const secondOperend = document.querySelector(".calculator__operend--right"); // calculator__operend--right 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
const calculatedResult = document.querySelector(".calculator__result"); // calculator__result 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
const operatorBtnAll = document.querySelectorAll(".operator");

function calculate(n1, operator, n2) {
  let result = 0;
  console.log(n1, operator, n2);
  if (operator === undefined) return n2;
  if (operator === "+") result = parseFloat(n1) + parseFloat(n2);
  if (operator === "-") result = parseFloat(n1) - parseFloat(n2);
  if (operator === "*") result = parseFloat(n1) * parseFloat(n2);
  if (operator === "/") {
    result = n1 / n2;
    console.log(n1, n2, result);
  }
  return String(result);
}

buttons.addEventListener("click", function (event) {
  // 버튼을 눌렀을 때 작동하는 함수입니다.

  const target = event.target; // 클릭된 HTML 엘리먼트의 정보가 저장되어 있습니다.
  const action = target.classList[0]; // 클릭된 HTML 엘리먼트에 클레스 정보를 가져옵니다.
  const buttonContent = target.textContent; // 클릭된 HTML 엘리먼트의 텍스트 정보를 가져옵니다.
  // ! 위 코드(Line 19 - 21)는 수정하지 마세요.
  if (target.matches("button")) {
    // TODO : 계산기가 작동할 수 있도록 아래 코드를 수정하세요. 작성되어 있는 조건문과 console.log를 활용하시면 쉽게 문제를 풀 수 있습니다.
    // 클릭된 HTML 엘리먼트가 button이면
    if (action === "number") {
      // 그리고 버튼의 클레스가 number이면
      // 아래 코드가 작동됩니다.
      console.log("숫자 " + buttonContent + " 버튼");
      if (
        secondOperend.textContent === "0" &&
        firstOperend.textContent === "0"
      ) {
        firstOperend.textContent = buttonContent;
      } else if (
        firstOperend.textContent !== "0" &&
        secondOperend.textContent === "0"
      ) {
        secondOperend.textContent = buttonContent;
      }
    }

    if (action === "operator") {
      console.log("연산자 " + buttonContent + " 버튼");
      operator.textContent = buttonContent;
    }

    if (action === "decimal") {
      // console.log('소수점 버튼');
    }

    if (action === "clear") {
      console.log("초기화 버튼");
      firstOperend.textContent = 0;
      secondOperend.textContent = 0;
      operator.textContent = "+";
      calculatedResult.textContent = 0;
    }

    if (action === "calculate") {
      console.log("계산 버튼");
      calculatedResult.textContent = calculate(
        firstOperend.textContent,
        operator.textContent,
        secondOperend.textContent
      );
    }
  }
});

// ! Advanced Challenge test와 Nightmare test를 위해서는 아래 주석을 해제하세요.

const display = document.querySelector(".calculator__display--for-advanced"); // calculator__display 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
let firstNum, operatorForAdvanced, previousKey, previousNum;
let count = 0;
let lastCalculate = 0;
let decimalCount = 0;
buttons.addEventListener("click", function (event) {
  // 버튼을 눌렀을 때 작동하는 함수입니다.

  const target = event.target; // 클릭된 HTML 엘리먼트의 정보가 저장되어 있습니다.
  const action = target.classList[0]; // 클릭된 HTML 엘리먼트에 클레스 정보를 가져옵니다.
  const buttonContent = target.textContent; // 클릭된 HTML 엘리먼트의 텍스트 정보를 가져옵니다.
  // ! 위 코드는 수정하지 마세요.

  // ! 여기서부터 Advanced Challenge & Nightmare 과제룰 풀어주세요.
  if (target.matches("button")) {
    if (action === "number") {
      if (
        display.textContent === "0" ||
        previousKey === "operator" ||
        previousKey === "calculate"
      ) {
        display.textContent = buttonContent;
      } else {
        display.textContent += buttonContent;
      }
      previousKey = "number";
    }
    if (action === "operator") {
      console.log("연산자 " + buttonContent + " 버튼");
      for (let i = 0; i < operatorBtnAll.length; i++) {
        let item = operatorBtnAll.item(i);
        if (item.textContent === buttonContent) {
          item.style.backgroundColor = "#00dac1";
        } else {
          item.style.backgroundColor = "#313132";
        }
      }
      if (previousKey === "operator") operatorForAdvanced = buttonContent;
      if (previousKey === "number" || previousKey === "calculate") {
        lastCalculate = display.textContent;
        if (count >= 1 && previousKey === "number") {
          lastCalculate = calculate(
            firstNum,
            operatorForAdvanced,
            display.textContent
          );
        }
        firstNum = lastCalculate;
        operatorForAdvanced = buttonContent;
        previousKey = "operator";
        count++;
        decimalCount = 0;
      }
    }
    if (action === "decimal") {
      if (decimalCount === 0) {
        if (previousKey === "operator") {
          display.textContent = `0${buttonContent}`;
          previousKey = "decimal";
        } else if (previousKey !== "decimal") {
          display.textContent += buttonContent;
          previousKey = "decimal";
        }
        decimalCount++;
      }
    }
    if (action === "clear") {
      firstNum = 0;
      operatorForAdvanced = undefined;
      display.textContent = 0;
      previousKey = "clear";
      decimalCount = 0;
      for (let i = 0; i < operatorBtnAll.length; i++) {
        let item = operatorBtnAll.item(i);
        item.style.backgroundColor = "#313132";
      }
    }
    if (action === "calculate") {
      if (previousKey !== "calculate") {
        previousNum = display.textContent;
        display.textContent = calculate(
          firstNum, // 3
          operatorForAdvanced, // undifind
          display.textContent // 3
        );
      }
      if (previousKey === "calculate") {
        display.textContent = calculate(
          firstNum,
          operatorForAdvanced,
          previousNum
        );
      }
      count = 0;
      firstNum = display.textContent;
      previousKey = "calculate";
      decimalCount = 0;
      for (let i = 0; i < operatorBtnAll.length; i++) {
        let item = operatorBtnAll.item(i);
        item.style.backgroundColor = "#313132";
      }
    }
    console.log("fisrtNum : " + firstNum);
    console.log("previousKey : " + previousKey);
    console.log("previousNum : " + previousNum);
  }
});

css는 다른 글에서

 

오늘 nightmare 난이도 까지 클리어 했습니다. advanced 까지는 쉽게 풀었는데 그 이후 Edge Case 대비 기능 개선에서 많이 힘들었습니다. 그럼, 코드의 주요부분에 대한 설명을 시작하겠습니다.

 

 

 

 

 

숫자판 부분


   if (action === "number") {
      if (
        display.textContent === "0" ||
        previousKey === "operator" ||
        previousKey === "calculate"
      ) {
        display.textContent = buttonContent;
      } else {
        display.textContent += buttonContent;
      }
      previousKey = "number";
    }

 우선 if문으로 number 클래스를 가진 숫자판들을 입력하면 작동되게 하였습니다. 이는 다른 버튼들에서도 마찬가지입니다. 숫자 입력에서 나올 수 있는 경우의 수는 2가지인데 

1.초기값0이 있거나 바로 이전에 연산자를 입력하여 숫자를 입력하면 현재값이 초기화되고 입력한 값이 들어오는 상황

2. 1이후에 display에 숫자가 있고, 입력한 값이 그 뒤에 붙어서 나오는 상황이 2가지이다. 이를 구현하기위해

  • 우선 if문으로 초기화되는 경우를 나누었다. display에 0이 들어 있거나 이전에 입력한 값의 종류를 기억하는 previousKey이 사칙연산과 = 인 경우에 초기화가 작동하게 만들었다.
  • 나머지는 else문에 넣어서 display뒤에 붙이도록 했다. 이때 display는 문자열 타입이기에  하면 사칙연산이 아니라 문자열이 합해지는 결과가 나온다.
  • 이후  공통적으로 숫자를 입력했다는 것을  알리기 위해  previousKey에 number을 넣었다.

 

연산자 부분


if (action === "operator") 
{
      if (previousKey === "operator") {
        operatorForAdvanced = buttonContent;
      }
      if (previousKey === "number" || previousKey === "calculate") 
      {
        
        if (count >= 1 && previousKey === "number") {
          display.textContent = calculate(
            firstNum,
            operatorForAdvanced,
            display.textContent
          );
        }
        
        firstNum = display.textContent;
        operatorForAdvanced = buttonContent;
        previousKey = "operator";
        count++;
        decimalCount = 0;
      }
}

연산자부분은 가장 복잡하고 시간이 많이 든 파트이다. 우선 나올 수 있는 경우의 수로는

1..previousKey가 연산자일 경우, 새로운 연산자를 넣고 종료.

2.previousKey가 숫자나 =일 때에 현재 display에 있는 숫자를 firstNum에 저장하고, 입력한 연산자를 operatorForAdvanced에 저장하고 previousKey에 연산자를 저장한다. 이때 count 변수를 1 증가시키고 .의 개수를 의미하는 decimanlCount를 0으로 초기화한다.

3.count는 =를 누를 때까지 입력한 연산자의 수를 의미한다. 즉, 다중 연산을 구분하기 위한 변수로 count가 1이상이면 =이 아니라도 calculate 함수로 연산을 한다.

 

 

 

 

소수점 부분


if (action === "decimal") {
      if (decimalCount === 0) {
        if (previousKey === "operator") {
          display.textContent = `0${buttonContent}`;
          previousKey = "decimal";
        } else if (previousKey !== "decimal") {
          display.textContent += buttonContent;
          previousKey = "decimal";
        }
        decimalCount++;
      }

 

소수점 부분에서 유의할 점은 .이 여러개가 되는 것을 막는 것이다. 이를 위해

1.decimalCount라는 변수를 만들어 0일 때만 작동하고 1을 증가시켜 중복을 막았다.

2.연산자를 입력 후 .을 입력하면 자동으로 0.으로 출력하게 만들었다.

3.다른 숫자가 있는 경우에는 그냥 뒤에 붙여주었다.

 

초기화 부분


    if (action === "clear") {
      firstNum = 0;
      operatorForAdvanced = undefined;
      display.textContent = 0;
      previousKey = "clear";
      decimalCount = 0;
      }

 엽력 값들을 초기화 시켜주는 부분으로 주요 포인트는  operatorForAdvanced에 undefind를 주는 것이다.

 

 

=부분


  if (action === "calculate") {
      if (previousKey !== "calculate") {
        previousNum = display.textContent;
        display.textContent = calculate(
          firstNum,
          operatorForAdvanced, 
          display.textContent 
        );
      }
      if (previousKey === "calculate") {
        display.textContent = calculate(
          firstNum,
          operatorForAdvanced,
          previousNum
        );
      }
      count = 0;
      firstNum = display.textContent;
      previousKey = "calculate";
      decimalCount = 0;
      }

두번째 난관 =이다. 그냥 함수로 넘기면 되지 않냐? 라고 물을 수 있지만 =을 연속적으로 누르면 이전 연산을 반복시킨다는 조건이 달려서 힘들었다. 간단히 설명하면

1.일반적인 경우는 firstNum과 display로 calculate함수를 돌린다

2.previousNum에 이전 연산 결과를 저장해 =가 연속으로 누르면 display 대신 previousNum으로 calculate함수를 돌린다.

3.이후 count들의 초기화를 진행한다.

 

 

 

 

느낀점


  • 이번 실습에서는 머리로는 상상할 수 있는데 입과 손으로는 구현하지 못한다는 답답함을 느낄수 있었다.
  • 이는 페어분도 마찬가지로 능력부족보다는 경험부족이 주된 원인이다. 
  • 오히려 이 부분이 더 마음에 드는데 현장에서 의사소통하는 연습을 할 수 있어 시행착오를 줄이는데  도움이 된다.
  • 다만 다 끝나고 레퍼런스를 보니 내 코드가 먼가 안 좋아 보인다. "방금까지 아름다워다고 생각하던 나의 생산라인~ 무슨 원숭이 새끼들 파파야 까먹으로고 줄서는 것처럼 보인다  "라는 어떤 대머리의 명언이 떠오르는데 딱 이 기분이다.
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함