[Let's get it 자바스크립트 프로그래밍] - 4.1~4.6 함수 사용하기_계산기

신혜린·2025년 1월 1일
0
post-thumbnail

계산기 UI 그리기

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <title>계산기</title>
  <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>
</head>

<!-- 계산기 버튼 구현 -->
<body>
  <input readonly id="operator">
  <input readonly type="number" id="result">
  <div class="row">
    <button id="num-7">7</button>
    <button id="num-8">8</button>
    <button id="num-9">9</button>
    <button id="plus">+</button>
  </div>
  <div class="row">
    <button id="num-4">4</button>
    <button id="num-5">5</button>
    <button id="num-6">6</button>
    <button id="minus">-</button>
  </div>
  <div class="row">
    <button id="num-1">1</button>
    <button id="num-2">2</button>
    <button id="num-3">3</button>
    <button id="divide">/</button>
  </div>
  <div class="row">
    <button id="clear">C</button>
    <button id="num-0">0</button>
    <button id="calculate">=</button>
    <button id="multiply">x</button>
  </div>
  <script>
    
  </script>
</body>  

script 작성하기 (feat. 순서도)

<script>
  let numOne = '';
  let operator = '';
  let numTwo = '';
  const $operator = document.querySelector('#operator');
  const $result = document.querySelector('#result');
</script>

1+2 를 실행시킨다면 ?

  • 1을 입력하면 numOne 변수에 저장한다
  • +을 누르면 operator 변수에 저장한다
  • 2를 누르면 [operator 변수에 값이 있는지 확인한 뒤], numTwo 변수에 저장한다.
  • =버튼을 누르면 [numTwo 변수에 값이 있는지 확인한 뒤], 계산 로직을 실행한다.
  • 연산자(=) 버튼을 누르기 전에는 operator 값이 있는지 확인한다.


고차함수로 중복 제거하기

계산기 로직에는 반복되는 구간이 많다. 이를 어떤 식으로 효율화 해야할까?

숫자마다 동일한 이벤트 리스너를 추가해야 한다.

document.querySelector('#num-0').addEventListener('click', () => {
  if (operator) {
    numTwo += 0;
  } else {
    numOne += 0;
  }
});

document.querySelector('#num-1').addEventListener('click', () => {
  if (operator) {
    numTwo += 1;
  } else {
    numOne += 1;
  }
});

-> 이런 식으로 반복되는 구간을 고차함수를 이용해서 중복 제거 할 수 있다.

고차함수(high order function)란?

⭐️ 함수가 함수를 반환하는 것을 고차함수 라고 한다.

const func = () => {
  return () => {
    console.log('hello');
  };
};
  • func 함수를 호출하면 함수를 반환한다.
const innerFunc = func(); // 호출()된 함수를 변수에 저장
innerFunc(); // hello
  • 반환된 함수는 다른 변수에 저장할 수 있고,
    변수에 저장된 함수를 다시 호출할 수도 있다.

함수가 호출된 코드(함수 이름 뒤에 ()가 붙은 코드)가 있는 경우,
호출된 함수 부분을 return 값으로 치환하면 이해하기 쉽다.
위 코드는 다음과 동일하다

const innerFunc = console.log('hello'); // return 값으로 치환
innerFunc(); // hello

return 하는 값을 바꾸고 싶은 경우, 매개변수를 이용하면 된다.

const func = (msg) => {
  return () => {
    console.log(msg);
  }
};
const innerFunc = func('hello');
const innerFunc2 = func('bye');

innerFunc(); // hello
innerFunc2(); // bye

func 함수처럼 바로 반환되는 값이 있으면 {return을 생략해서 다음과 같이 나타낼 수 있다.

const func = (msg) => () => {
  console.log(msg);
};
  • 화살표가 연이어 나오는 함수는 고차함수를 의미한다.

고차함수를 이용해서 이벤트 리스너 내 중복 로직 제거하기

const onClickNumber = (number) => (event) => {
  if (operator) {
    numTwo += number;
  } else {
    numOne += number;
  }
  $result.valut += number;
};

위 고차함수를 이용한다면 이벤트 리스너마다 로직을 중복 작성하는 것을 피할 수 있다.

document.querySelector('#num-0').addEventListener('click', onClickNumber('0'));
document.querySelector('#num-1').addEventListener('click', onClickNumber('1'));                    

operator 연산자 고차함수 이용하기

const onClickOperator = (op) => () => {
  if (numOne) {
    operator = op;
    $operator.value = op;
  } else {
    alert('숫자를 먼저 입력하세요.');
  }
};
document.querySelector('#plus').addEventListener('click', onClickOperator('+'));
document.querySelector('#minus').addEventListener('click', onClickOperator('-'));

고차함수를 이용하면 이전보다 훨씬 간결해진 코드를 볼 수 있다.
입력되는 숫자만 다를 뿐, 내부 로직은 동일하기 때문에 숫자값만 매개변수로 받아서 리턴해주면 된다.

일반함수를 이용해서 이벤트 리스너 내 중복 로직 제거하기

고차함수 대신 일반함수를 사용하고 싶다면, 버튼을 클릭할 때 버튼 내부의 문자를 event.target.textContent로 가져올 수 있는 점을 활용할 수 있다.

const onClickNumber = (event) => {
  if (operator) {
    numTwo += event.target.textContent;
  } else {
    numOne += event.target.textContent;
  }
  $result.valut += event.target.textContent;
};

고차함수가 필요한 이유

하지만, 실무에서는 계산기 로직처럼 '동일한' 함수보다 '대부분 비슷한데 특정 부분만 다른' 함수가 더 많이 나오기 때문에, 이런 경우엔 고차함수를 사용해야 한다.



중첩 if문 줄이기

if문이 3단계 이상 중첩되면 코드 읽기가 점점 어려워지기 때문에 지양한다.

중첩 줄이는 방법

  1. if문 다음에 나오는 공통된 절차를 각 분기점 내부에 넣는다.
  2. 짧은 절차부터 실행되도록 if문을 작성한다.
  3. 짧은 절차가 끝나면 return 이나 break를 사용하여 중단한다.
  4. else를 제거한다.
  5. 중첩된 분기점이 나올 때마다 1~4번을 반복한다.

early return 관련 문서 참고하기

switch 사용

연산(calculate) 조건에 사용되는 변수가 operator로 같기 때문에 if문보다 switch문을 사용하는 게 더 깔끔하다.

document.querySelector('#calculate').addEventListener('click', () => {
  if (numTwo) {
    swith (operator) {
      case '+':
        $result.value = parseInt(numOne) + parseInt(numTwo);
        break;
      case '-':
      	$result.value = numOne - numTwo;
      	break;
      case '*':
      	$result.value = numOne * numTwo;
      	break;
      case '/':
      	$result.value = numOne / numTwo;
      	break;
      default:
      	break;
    }
  }
});

덧셈(+)의 연산에서 parseInt 를 사용하는 이유 ?

javascript는 + 연산을 문자열에서도 사용할 수 있기 때문에 문자열로 저장한 numOnenumTwo를 연산하면 4 + 5 = 45 의 결과가 나오게 된다.

eval 메소드

문자열을 자바스크립트 코드처럼 실행하는 방법
eval('1 + 2'); // 3

  • eval 메소드를 사용한다면 덧셈 연산을 다음과 같이 수정할 수 있다.
case '+':
  $result.value = eval(numOne + operator + numTwo);
  break;

⭐️ 하지만 eval 함수를 남용하면 해커가 악용할 가능성이 있다. eval 함수에 문자열을 입력하면 그대로 실행되므로 해커가 이를 통해 프로그램에 위험한 코드를 실행할 수도 있기 때문에, 실무에서는 eval 함수의 사용을 피하는 것이 보안상 안전하다.



최종 소스코드

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>계산기</title>
  <style>
    * { box-sizing: border-box }
    body { margin: 0; padding: 0; }
    .flex { display: flex; height: 100vh; justify-content: center; align-items: center; }
    .content { 
      padding: 5px; border: 1px solid black; 
      box-shadow: 4px 4px 4px 1px rgba(0, 0, 0, 0.3); 
    }
    .title { font-size: large; font-weight: bold; text-align: center; }
    .number-container { display: inline-block; position: relative; }
    /* preview 문자열이 일정 길이 이상 초과되면 ...으로 표시한다. */
    .preview { 
      position: absolute; top: 3px; left: 10px; margin: 0px; 
      width: 170px; text-align: right; font-size: small; color:gray; 
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    #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>
</head>

<body>
  <section class="flex">
    <div class="content">
      <p class="title">계산기</p>
      <input readonly id="operator">
      <div class="number-container">
        <p class="preview"></p>
        <input readonly type="number" id="result">
      </div>
      <div class="row">
        <button id="num-7">7</button>
        <button id="num-8">8</button>
        <button id="num-9">9</button>
        <button id="plus">+</button>
      </div>
      <div class="row">
        <button id="num-4">4</button>
        <button id="num-5">5</button>
        <button id="num-6">6</button>
        <button id="minus">-</button>
      </div>
      <div class="row">
        <button id="num-1">1</button>
        <button id="num-2">2</button>
        <button id="num-3">3</button>
        <button id="divide">/</button>
      </div>
      <div class="row">
        <button id="clear">C</button>
        <button id="num-0">0</button>
        <button id="calculate">=</button>
        <button id="multiply">x</button>
      </div>
    </div>
  </section>
  <script>
    let numOne = '';
    let operator = '';
    let numTwo = '';
    const $operator = document.querySelector('#operator');
    const $result = document.querySelector('#result');
    const onClickNumber = (event) => {
      if (!operator) {
        numOne += event.target.textContent;
        $result.value += event.target.textContent;
        return;
      }
      if (!numTwo) {
        $result.value = '';
      }
      numTwo += event.target.textContent;
      $result.value += event.target.textContent;
    };
    document.querySelector('#num-0').addEventListener('click', onClickNumber);
    document.querySelector('#num-1').addEventListener('click', onClickNumber);
    document.querySelector('#num-2').addEventListener('click', onClickNumber);
    document.querySelector('#num-3').addEventListener('click', onClickNumber);
    document.querySelector('#num-4').addEventListener('click', onClickNumber);
    document.querySelector('#num-5').addEventListener('click', onClickNumber);
    document.querySelector('#num-6').addEventListener('click', onClickNumber);
    document.querySelector('#num-7').addEventListener('click', onClickNumber);
    document.querySelector('#num-8').addEventListener('click', onClickNumber);
    document.querySelector('#num-9').addEventListener('click', onClickNumber);
    const onClickOperator = (op) => () => {
      if (numOne) {
        operator = op;
        $operator.value = op;
      } else {
        alert('숫자를 먼저 입력하세요.');
      }
    };
    document.querySelector('#plus').addEventListener('click', onClickOperator('+'));
    document.querySelector('#minus').addEventListener('click', onClickOperator('-'));
    document.querySelector('#divide').addEventListener('click', onClickOperator('/'));
    document.querySelector('#multiply').addEventListener('click', onClickOperator('*'));
    document.querySelector('#calculate').addEventListener('click', (event) => {
      if (numTwo) {
        switch (operator) {
          case '+':
            $result.value = parseInt(numOne) + parseInt(numTwo);
            break;
          case '-':
            $result.value = numOne - numTwo;
            break;
          case '*':
            $result.value = numOne * numTwo;
            break;
          case '/':
            $result.value = numOne / numTwo;
            break;
          default:
            break;
        }
        $operator.value = '=';
        numOne = $result.value;
        numTwo = '';
        operator = '';
        isCalculate = true;
        $preview.textContent += event.target.textContent + $result.value;
      } else {
        alert('숫자를 먼저 입력하세요.');
      }
    });
    const clear = () => {
      numOne = '';
      operator = '';
      numTwo = '';
      $operator.value = '';
      $result.value = '';
      $preview.textContent = '';
      isCalculate = false;
    }
    document.querySelector('#clear').addEventListener('click', clear);
  </script>
</body>
</html>

profile
개 발자국 🐾

0개의 댓글