Hanachoi 2022. 5. 27. 21:34

#순서도 작성하기

  • 우리가 계산기를 만들 때는 세세한 부분까지 다 신경을 써서 만들어야 한다. 
  • 우리가 100을 입력한다면 단순히 100이란 숫자만 생각할 것이 아니라, 1 , 0 , 0 을 따로 눌러서 입력을 하는 것이고 이게 합쳐져서 100을 만든다고 생각해야된다. 
  • 첫번재 숫자를 입력하고 연산자를 누른 뒤에, 두번째 숫자를 안 누르고 그냥 바로 =을 누르게 되면 오류가 날 것 까지 생각해야 한다. 
  • 한 계산을 마무리하고 다음 계산을 연달아 계산할때는 어떻게  처리해야 할 지도 생각해야된다. 
  1. 시작
  2. 숫자를 입력한다
  3. 연산자를 입력한다
  4. 숫자를 입력한다
  5. 계산한다
  6. 결과를 표시한다 
  • 간단하게 생각한 위 순서도를 코드를 짤 때는 어떻게 구체적으로 짜야되는 지 생각하면서 구체화한다

1-1. 시작 -> 숫자 1을 저장할 변수를 만든다 -> 연산자를 저장할 변수를 만든다 -> 숫자 2를 저장할 변수를 만든다. -> 대기

1-2. 숫자버튼클릭 -> 숫자를 변수에 저장한다 ->대기

1-3. 연산자 버튼 클릭 -> 연산자를 변수에 저장한다 ->대기

1-4. =버튼클릭 ->숫자1과 숫자2를 연산자를 적용해 계산한다 -> 계산 결과를 화면에 출력한다 ->끝

 

1-2-1.  [숫자를 변수에 저장한다]를 더 세분화 해준다

operator 변수가 비어있는가? Yes : numOne변수에 숫자를 저장한다. No : numTwo변수에 숫자를 저장한다. 

 

  • 변수의 관계도 생각해보자. numOne이 존재해야 operator 존재 가능 -> operator가 존재해야 numTwo 가 존재가능 => numTwo가 존재한다는 뜻은 numOne과 operator가 존재한다는 뜻이다. 

1-3. 순서도 수정 : 연산자버튼 클릭 ->numOne값이 존재하는가? yes : 연산자를 변수에 저장한다 -> 대기 No : alert창 띄우기

1-4. 순서도 수정 : =버튼 클릭 -> numTwo 값이 존재하는가? yes : 숫자1과 숫자2를 연산자를 적용해 계산한다.-> 계산 결과를 화면에 출력한다 ->끝 No : alert창 띄우기 

 

#함수 중복 제거하기(고차함수)

  • 기본적인 계산기 틀
<!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>
    let numOne = "";
    let operator = "";
    let numTwo = "";
    const $operator = document.querySelector("#operator");
    const $result = document.querySelector("#result");
    document.querySelector("#num-0").addEventListener("click", () => {});
    document.querySelector("#num-1").addEventListener("click", () => {});
    document.querySelector("#num-2").addEventListener("click", () => {});
    document.querySelector("#num-3").addEventListener("click", () => {});
    document.querySelector("#num-4").addEventListener("click", () => {});
    document.querySelector("#num-5").addEventListener("click", () => {});
    document.querySelector("#num-6").addEventListener("click", () => {});
    document.querySelector("#num-7").addEventListener("click", () => {});
    document.querySelector("#num-8").addEventListener("click", () => {});
    document.querySelector("#num-9").addEventListener("click", () => {});
    document.querySelector("#plus").addEventListener("click", () => {});
    document.querySelector("#minus").addEventListener("click", () => {});
    document.querySelector("#divide").addEventListener("click", () => {});
    document.querySelector("#multiply").addEventListener("click", () => {});
    document.querySelector("#calculate").addEventListener("click", () => {});
    document.querySelector("#clear").addEventListener("click", () => {});
  </script>
</body>
  • 기본적으로 숫자키마다 숫자 자체는 다르지만 동작원리는 똑같기 때문에 코드의 중복이 많이 발생한다. 
  • 지금 현재 보여지는 자바스크립트 페이지 정도의 중복은 줄이지 않아도 되지만 이 버튼에 각각 붙을 함수는 동일한 원리로 움직일 것이기 때문에 중복을 생략해 줄 수 있다. 
  • [숫자의 변수를 저장한다.] 각각의 번호키에 함수를 달아준다.  밑의 코드에서 0 숫자 부분은 각각의 키에 맞게 숫자를 변경해준다. 이 함수의 의미는 operator가 저장되어 잇으면 첫번째 숫자는 이미 등록되어 있다고 보고 두번째 숫자 자리에 숫자를 입력해 주는 것이고, 만약 operator 자리가 비어있다면 첫번째 숫자도 아직 등록이 되어있지 않다고 보고, 첫번째 자리에 숫자를 입력해주는 식이다. 
if(operator) {
	numTwo += '0';
}else{
	numOne += '0';
}
$result.value = '0'; //해당 값을 result 칸에 보여주기//
  • 이렇게 동일한 코드를 각각의 번호키에 달아주면 굉장한 반복작업을 하게 되는 것이다. 이런 반복적인 코드를 내가 만들고 있다면 무엇인가 잘못됨을 느끼고 어떻게 코드를 압축시킬 수 있을지 생각해야 한다. 
  • 항상 데이터의 변수값을 바꿨다면 해당 내용을 화면에 표시하는 부분까지 수정해야 된다는걸 잊지말자. 
  • 함수에서 중복을 제거할때는 달라지는 특정 부분들을 매개변수로 만들면 된다. 지금 여기서도 각각의 번호키에 들어가는 함수의 모습은 똑같은데 번호가 다르기때문에  숫자부분이 모두 다르게 되어있다. 그럼 숫자부분을 매개변수로 만들면된다.  매개변수로 만들면 좋은 점은 매개변수 자리에 각각의 숫자를 넣어줄 수 있기 때문에 좋다.
  • 위의 코드에서 매개변수 부분을  number로 받아준다. 그리고 각각의 버튼에 onClickNumber함수를 달아주고 매개변수 자리에 각각의 숫자를 넣어준다.
  • 이정도만 해도 중복을 많이 제거한 것이지만, 실제로 실행을 해봤을때 정확한 결과는 나오지 않는다. 이유는?
const onClickNumber = (number) =>{
      if(operator){//비어있지않다
        numTwo += number;
      }else{//비어있다.
        numOne += number;
      }
      $result.value += number;
      //return undefined;
    
    };
    
    document.querySelector("#num-0").addEventListener("click", onClickNumber('0'));
    document.querySelector("#num-1").addEventListener("click", onClickNumber('1'));
    document.querySelector("#num-2").addEventListener("click", onClickNumber('2'));
    document.querySelector("#num-3").addEventListener("click", onClickNumber('3'));
    document.querySelector("#num-4").addEventListener("click", onClickNumber('4'));
    document.querySelector("#num-5").addEventListener("click", onClickNumber('5'));
    document.querySelector("#num-6").addEventListener("click", onClickNumber('6'));
    document.querySelector("#num-7").addEventListener("click", onClickNumber('7'));
    document.querySelector("#num-8").addEventListener("click", onClickNumber('8'));
    document.querySelector("#num-9").addEventListener("click", onClickNumber('9'));
  • 함수는 기본적으로 안에 [return undefined;]를 포함하고 있다고 보면 된다. 우리가 이벤트리스너에 onClickNumber('')부분은 함수자리인데 거기에 undefined를 넣은 셈이 된다. 여기에  undefined가 아니라 함수를 넣어줘야 되는 상황인 것이다. 그래서 return undefined를 함수로 바꿔준다.
  • 하지만 onClickNumber 는 버튼이 클릭되었을때 실행되는 함수인데, return부분에 아무것도 들어있지 않다면 코드가 실행되지 않을 것이다. 그래서 그 안에 내용을 채워줘야 한다. 그래서 return () => {} 부분에 클릭되었을 때 실행되어야 될 내용을 직접 넣어줘야 한다. 
const onClickNumber = (number) =>{
      return () =>{ 
        if(operator){//비어있지않다
        numTwo += number;
      }else{//비어있다.
        numOne += number;
      }
      $result.value += number;

      };
    };
  • 여기서 함수를 하나 더 줄일 수 있게 된다. 화살표 함수에서 중괄호와 return이 붙으면 생략이 가능해진다. 화살표가 연달아 나오는 모양이 된다. 함수 안에 함수가 있다. 함수가 함수를 return하고 있다 이렇게 생각하면 된다. 
  • 함수가 함수를 리턴하는 형식을 고차함수(high order function)라고 부른다.
const onClickNumber = (number) => (event) =>{ 
        if(operator){//비어있지않다
        numTwo += number;
      }else{//비어있다.
        numOne += number;
      }
      $result.value += number;

      };
  • 여기서 두번째 함수의 () 안에는 event가 들어가 있다. 이 event 매개변수는 브라우저가 넣어주는 매개변수이다. 그리고 이 함수를 브라우저가 몰래 실행을 시켜주고 이벤트 객체를 만들어서 함수 안에 넣어주게 된다. 즉, 이 함수가 들어가 있으면, 클릭이 발생할때 브라우저가 알아서 event인수를 넣어주고 그 객체를 넣은 함수를 알아서 실행시켜 주게 되는 것이다.
  • 함수들이 있는데 그 함수들이 중복된다고 생각되면, 고차함수를 쓸 수 있다. 
  • 화살표가 연달아 나온게 있다면 순서대로 생각을 해보면 된다. 

 

  • 지금 이 코드에서는 사실 고차함수를 쓰지 않아도 동일한 원리를 코드로 움직이게 할 수있다. 우리가 html로 만든 화면에서 각각의 숫자버튼은 textContent로 구성이 되어있다. 
  • querySelector로 각각의 숫자버튼을 선택했고 개들의 textContent는 각각의 숫자가 될 것이다. (ex. 버튼 0의 textContent는 0이 된다.) 여기에 number대신 event.target.textContent를 달아주면 화면에 적힌 내용을 그대로 가져올 수 있게 된다. 
   const onClickNumber = (event) =>{ 
        
        if(operator){
        numTwo += event.target.textContent;
      }else{
        numOne += 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);
  • 우리가 event,target.textContent라는 기능을 통해서 화면의 글자를 그대로 가져올 수있다는걸 몰랐다고 해도, 고차함수를 응용해서 똑같은 원리를 구현해 낼 수 있다. 

 

  • 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('-'));
    document.querySelector("#divide").addEventListener("click",onClickOperator('/') );
    document.querySelector("#multiply").addEventListener("click",onClickOperator('*'));
  • 하지만 여기서 문제점이 하나 발생한다. 우리가 operator를 하나 선택했으면 numTwo로 넘어가야되기 때문에 numOne안에 작성된 숫자가 지워 진 다음에 다음 숫자가 작성되어야 한다. 

# if문 중첩 줄이기

  • 지금까지 작성한 코드는 3+4를 하면 7이 아니라 그냥 문자가 합쳐져서 34가 되어버리는 상황이다. 여기서 numTwo가 없는 상황에서는 화면의 숫자를 지워줘야 한다. numTwo가 없는 상황은 언제이지? numTwo를 처음 입력할때는 얘가 비어있을 것이다.
  • const onClickNumber 에 if문을 하나 더 추가해서 상황을 정리해줄 수 있다. 
  • if문 조건식에 numTwo가 없을때, 화면을 먼저 비워준다음에 다음에 입력되는 숫자가 표시되도록 만들어 준다. 
  if(operator){//비어있지않다
          if(!numTwo){
            $result.value = '';
          }
        numTwo += event.target.textContent;
      }else{//비어있다.
        numOne += event.target.textContent;
      }
      $result.value += event.target.textContent;

      };

 

  • 주의할 점은 if문이 계속적으로 중첩될 수 있지만, 이렇게 되면 코드가 어떻게 작동하는지 정말 헷갈리기 시작한다. if문을 중첩시키지 않고 코드를 작성하는게 가장 좋은 방법이다.
  • 중첩을 제거하는 방법
    • 1. if문 다음에 나오는 공통된 절차를 각 분기점 내부에 넣는다.
    • 2. 분기점에서 짧은 절차부터 실행하게 if 문을 작성한다.
    • 3. 짧은 절차가 끝나면 return(함수 내부의 경우)이나 break(for문 내부의 경우)로 중단한다.
    • 4. else를 제거한다(이때 중첩 하나가 제거된다)
    • 5. 1~4의 절차를 계속 반복한다.
  • 1. 공통된 절차를 각 분기점 내부에 넣는다. : 우리가 저번 시간에 if-else구간에서 중복된 코드가 있으면 if-else구문 밖으로 아에 빼와서 공통된 코드를 실행시켜 줄 수 있었다. 그것과 반대로, 이번에는 중복되는 절차가 있다면 각각의 조건식에 동일하게 붙여준다. 중복된 코드를 넣을때는 if 문 다음에 나오는 공통된 절차만 각 분기점 내부에 넣어주면 된다. if문 전에 있는 애들은 다시 안넣어줘도됨.
  • 2. 분기점에서 짧은 절차부터 실행하게 if 문을 작성한다. : if문과 else문을 비교해봤을 때, 둘 중 더 짧은 절차를 먼저 실행되도록 구문을 바꿔줄 수있다. 지금 위의 코드에서는 else문이 더 짧기 때문에 먼저 실행될 수 있도록 if 문으로 자리를 옮겨준다. 옮겨줄때 주의 사항은 if(조건식)에서 조건식 부분을 반대로 바꿔줘야 된다. 반대로 바꿀꺼면 ! 를 붙여주면 되겠지.
  //(operator)부분을 ! 붙여서 반대로 만들어주기//
  if(!operator){//비어있다.로 변경되었다.
        numOne += event.target.textContent;
        $result.value += event.target.textContent;
         
      }else{//비어있지 않다. 로 변경되었다.
        if(!numTwo){
            $result.value = '';
          }
        numTwo += event.target.textContent;
        $result.value += event.target.textContent;
      }
  • 3. 짧은 절차가 끝나면 return(함수 내부의 경우)이나 break(for문 내부의 경우)로 중단한다. : 지금 여기선 함수의 내부니까 return으로 함수를 중단하면 된다. 그래서 if문에 return;을 붙여주었다.
 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;
      
      };
  • 4. else를 제거한다(이때 중첩 하나가 제거된다) :  return이 나오면 else부분을 지워줄 수 있게 된다. 지금 여기서 else문을 줄여줘서 단계가 더 축소되었다.  else문안에 있던 if절이 밖으로 튀어나왔다. 이렇게 되면 if문안에 또 다른 if문이 들어가게 되는걸 제거할 수 있다. 그래서 코드를 읽기 더 간단해짐. 

 

#결과 계산하기

  • numOne, numTwo에서 받는 숫자들은 문자열로 구성되어 있어서 숫자로 먼저 바꿔줘야 한다. 
  • 그런데 왜+만 parseInt를 붙여줬을까?  플러스 연산자에서는 문자열끼리 더하면 그냥 문자열로 출력이 되지만(숫자로 자동으로 바뀌지 않는다), 마이너스, 곱하기, 나누기 연산자에서는 문자열끼리 계산을해도 알아서 숫자로 바꿔준다. 그래서 플러스 연산자만 지금 parseInt로 받는 숫자들을 감싸준 것이다. 만약 헷갈리면 그냥 모든 것에 똑같이 parseInt 달아줘버리자.
    
    document.querySelector('#calculate').addEventListener('click', () => {
        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;
          }
        } else {
          alert('숫자를 먼저 입력하세요.');
        }
      });

 

#초기화하기

  • 새로고침을 하면 계산기가 초기화 되기는 하지만 우리는 clear버튼을 만들어놨기 때문에 얘를 누르면 숫자와 연산자가 모두 지워지도록 코드를 작성해준다.
  • 모든 변수를 초기상태로 되돌아 가도록 비워준다.
       
    document.querySelector("#clear").addEventListener("click",()=>{
      numOne = '';
      perator = '';
      numTwo = '';
      $operator.value = '';
      $result.value = '';

    });
  • 초기상태가 뭔지 정확하게 알기 위해서는 초기상태의 코드를 <script>태그 밑에 바로 몰아두면 편하다. 그대로 가지고 오면 빠짐없이 모든 상태를 초기화시킬 수 있다.