제로초 JavaScript 강의 - 계산기 순서도 그리기, 함수 중복 제거하기(고차함수), 중첩 if 문 제거하기, 결과 계산하기, 초기화

Study/JavaScript

 

  • 제로초 JavaScript 강의 - 계산기 순서도 그리기, 함수 중복 제거하기(고차함수), 중첩 if 문 

       제거하기, 결과 계산하기, 초기화




Review

 

 

 순서도 그리기가 정말 쉽지 않다.

어떠한 변수가 발생할지 몰라서 정말 정교하게 작성해야하는데, 처음에는 생각나는대로 그리고 나서

하나하나 수정해나가는 것이 좋다고 한다. 그렇게 코드를 짜는 것이 더 복잡해지지 않을까 싶은데

뭐가 정답인지 잘 모르겠다.

어렵긴 하지만 제로초님께서 차근차근 알려주셔서 덕분에 재밌게 강의를 듣고 있다.

완강까지 파이팅해보자 - !

 

 

 


 

  • 제로초 JavaScript 강의 - 계산기 순서도 그리기, 함수 중복 제거하기(고차함수), 중첩 if 문 

       제거하기, 결과 계산하기, 초기화

 

 

 

<계산기 순서도 그리기>

 

: 계산기에 대한 순서도를 생각나는 대로 작성한 것이다.

 

계산기 순서도

 

 

 

하지만 자바스크립트 프로그램의 순서도를 만들 때 사용자 이벤트를 기준으로 순서도를 끊어야 한다고

했다. 계산기는 사용자가 숫자나 연산자(더하기, 빼기, 나누기, 곱하기 등) 버튼을 클릭해 여러 가지 계산을

하는 장치다. 따라서 클릭 이벤트가 많이 발생한다.

또한, 사용자가 입력한 숫자나 연산자를 저장하고 있어야 = 버튼을 클릭했을 때 결과를 계산할 수 있다.

1 + 2라는 계산을 해야 한다면 프로그램은 1과 +와 2 모두를 저장해야 한다.

따라서 프로그램이 시작될 때 숫자 두 개와 연산자를 저장할 변수부터 마련해야 한다.

 

각각의 경우를 고려해 순서도를 다시 그려 보자.

 

 

재작성한 계산기 순서도 1

 

이렇게 순서도를 그림처럼 쪼갤 수 있다면 훌륭한 것이라고 하셨다. 어렵당,,

 

순서도를 그린 후에는 예시를 들어 검증해야 한다. 1 + 2로 확인해 보자.

숫자 1, 연산자, 숫자 2를 저장하는 변수의 이름을 각각 numOne, operator, numTwo라고 하자.

1을 누르면 어떻게 될까? 숫자 버튼을 클릭했으니 숫자를 변수에 저장한다.

여기서 numOne 변수에 저장할지 numTwo 변수에 저장할지 정해야 한다. 이는 operator 변수에 값이

저장되어 있느냐 아니냐에 따라 둘 중 어느 변수에 저장할지를 판단하면 된다.

 

 

판단하기

 

1을 numOne 변수에 저장한 후 + 버튼을 누르면 +가 operator 변수에 저장된다. 그러고 나서 2를 누르면 

operator 변수에 값이 저장되어 있으므로(+가 저장되어 있다) numTwo 변수에 2가 저장된다.

 

마지막으로 = 버튼을 누르면 numOne에 저장되어 있는 1과 numTwo에 저장되어 있는 2를 operator

저장되어 있는 +로 계산한다.

 

여기서 끝이 아니다. 가능한 한 다양한 경우의 수를 생각해 순서도의 완성도를 높여야 한다.

이번에는 10 - 5를 생각해 보자. 먼저 1을 누르면 1은 numOne 변수에 저장된다.

그다음에 0을 누르면 아직 operator 변수에 값이 저장되어 있지 않으므로 0도 numOne 변수에 저장해야

한다. 하지만 numOne에는 이미 1이 저장되어 있는데 어떻게 0을 추가로 저장할 수 있을까?

단순히 1에 0을 더한다고 생각하면 1 + 0 = 1이라 그저 1이 된다. 

 

하나의 변수에 여러 값을 저장할 때 사용했던 것으로 배열이 있다. 처음 1을 저장할 때 [1]로 저장하고,

그다음 0을 저장할 때 두 번째 요소로 추가해 [1, 0]을 저장할 수 있다. 그리고 배열의 요소들을 배열에서

제공하는 join 메서드를 사용해 다음 코드처럼 문자열로 합친 후 숫자로 바꾸면 된다.

 

[1, 0].join('') === '10'
Number([1, 0].join('')) === 10

 

배열보다 조금 더 단순한 방법도 있다. 문자열을 사용하는 방법인데, 문자열은 서로 더하면 이어지는

성질이 있다. '자바' + '스크립트' === '자바스크립트'이듯이 '1' + '0' 1이 아니라 '10' 이다.

애초에 numOne numTwo에 숫자가 아니라 문자열을 저장한다면 숫자 버튼을 여러 번 클릭해 쉽게

더할 수 있다. = 버튼을 눌러 계산할 때 문자열인 numOne numTwo를 숫자로 바꿔서 계산하기만

하면 된다.

 

이처럼 같은 내용을 구현하더라도 여러 방식으로 구현할 수 있다. 방식마다 성능은 다를 수 있지만,

입문 단계이니 성능보다는 구현을 먼저 생각하자.

 

 

이번에는 숫자를 누르지 않고 연산자 버튼이나 = 버튼을 먼저 누른 경우를 생각해 보자.

 

이럴 때는 경고 메시지를 띄우는 게 좋을 것 같다. 경고 메시지를 띄울 때는 alert 함수를 사용한다.

연산자 버튼을 누를 때는 numOne에 값이 있는지 확인하고, = 버튼을 누를 때는 numTwo에 값이 있는지

확인하면 된다. 사실 = 버튼을 누를 때 numOne, operator, numTwo 변수에 모두 값이 저장되어 있어야

하지만, 애초에 numOne이나 operator 변수의 값이 없다면 numTwo 변수에도 값이 있을 수 없다. 

numOne 변수의 값이 존재할 때 operator 변수에 연산자를 저장할 수 있고, operator 변수의 값이 존재할

 numTwo 변수에 숫자를 저장하기 때문이다. 이렇게 논리적으로 생각해 보면 판단하는 절차를

간소화할 수 있다. 다시 이야기하지만, 절차가 간소화될수록 프로그램은 더 효율적으로 되고 성능도

좋아진다.

 

변수의 관계

 

 

이 내용을 바탕으로 순서도를 다음과 같이 수정한다.

 

재작성한 계산기 순서도 2

 

 

 

<계산기 화면 달고 이벤트 달기>

 

: 순서도를 만들었으니 순서도를 기반으로 계산기 화면부터 만들어 보자.

webgame 폴더calculator.html 파일을 만들고 다음과 같이 코드를 작성한다.

(파일 생성 방법은 3.2 HTML 화면 만들기를 참고)

 

 

<!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>

 

입력이 끝나면 Ctrl+S를 눌러 저장한다.

webgame 폴더에 calculator.html이 보이면 더블 클릭해 실행한다. 또는 에디터에서 F5r를 누른 후 Chrome을 선택해도 실행할 수 있다. 실행하고 나면 크롬 브라우저에 계산기 모양이 보인다.

 

실행결과

 

이제 각 버튼에 이벤트 리스너를 달아야 한다.

버튼이 많은 만큼(16개나 됩니다) 어떻게 달면 효율적일지 고민해야 한다. 어떻게 할지 떠오르지 않는다면

이벤트 리스너를 모두 달고 나서 개선점을 찾는 것도 방법이다.

 

 

calculator.html <script> 부분을 작성하자.

 

<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>

 

 

반복되는 부분이 엄청 많다. 이럴 때는 일부만 작성한 다음에 규칙을 찾는 것이 좋다. 

#num-0 #num-1 태그만 생각해 보겠다. 참고로 numOne numTwo를 숫자가 아닌 문자열로 저장한

이유가 있다. 이것은 조금 뒤에 알아보자.

 

 

숫자 버튼 클릭 부분 순서도

 

 

그림 4-7에 따라 다음과 같이 코드를 작성할 수 있다.

 

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

 

numOne numTwo를 문자열로 만든 이유가 여기에 나온다.

 

만약 34 + 26을 계산하고 싶다면 3, 4, +, 2, 6 버튼을 순서대로 클릭해야 한다. 여기서 3을 누른 후 4를

누를 때가 문제다. numOne이 숫자이면 3과 4가 더해져 34가 아니라 7이 된다. 그래서 문자열로 만들어

 '3' + '4' === '34'가 되게 만들었다.

일단 문자열로 저장한 다음, 나중에 계산할 때 숫자로 바꾼다. $result.value 부분은 순서도에는 없지만

변수에 저장한 값을 화면에 표시하기 위한 코드이다.

 

코드를 보면, 0과 1 같은 숫자를 제외하고는 나머지 부분이 모두 같다. 이렇게 #num-9까지 작성하다 보면

중복되는 부분이 많이 나온다. 그런데 완전히 똑같은 것이 아니라 0, 1, ..., 9 같은 숫자는 달라서 어떻게

중복을 제거해야 할지 애매하다.

 

 

 

 

<고차함수로 중복 제거하기>

 

:  중복을 제거하고 싶으면 함수의 특성을 이용한다.

함수는 호출하면 어떤 값을 반환한다. 이 값은 숫자나 문자열, 불 값 등으로 제한되어 있지 않고

자바스크립트의 모든 자료형이 될 수 있다. 즉, 함수가 함수를 반환할 수도 있다. 

 

const func = () => {
  return () => {
    console.log('hello');
  };
};

 

func 함수를 호출하면 함수를 반환한다. 반환된 함수는 다른 변수에 저장할 수 있고 변수에 저장된 함수를

다시 호출할 수도 있다.

 

const innerFunc = func();
innerFunc(); // hello

 

이해하기 어렵다면 func() 부분을 반환한 값으로 대체하면 됩니다. 앞 코드는 다음 코드와 같다.

 

const innerFunc = () => {
  console.log('hello');
};
  innerFunc(); // hello

 

함수가 호출된 코드(함수 이름 뒤에 ()가 붙은 코드)가 있다면 그 부분을 실제 return 값으로 치환하면

이해하기 쉽다.

func 함수는 hello라는 문자열을 console.log 하는 함수를 찍어 내는 공장이라고 생각할 수 있다. 

func 함수를 호출할 때마다 반환한 함수가 생성된다.

 

const innerFunc1 = func();
const innerFunc2 = func();
const innerFunc3 = func();
...

 

hello라는 문자열을 다른 값으로 바꾸고 싶다면 어떻게 해야 할까?

반환하는 값을 바꾸고 싶을 때는 매개변수를 사용한다. 즉, 바꾸고 싶은 자리를 매개변수로 만들면 된다.

다음 코드와 같이 hello 문자열을 msg 매개변수로 바꿔 보겠다.

 

const func = (msg) => {
  return () => {
    console.log(msg);
  };
};

 

이제 func 함수를 호출하면, func 함수에 넣은 매개변수를 console.log 하는 함수가 반환된다.

 

const innerFunc1 = func('hello');
const innerFunc2 = func('javascript');
const innerFunc3 = func();
innerFunc1(); // hello
innerFunc2(); // javascript
innerFunc3(); // undefined

 

마찬가지로 func 함수 호출 부분을 return 값으로 대체해 보면 이해될 것이다. 이때 매개변수 위치에는

실제 값을 넣어야 한다.

 

const innerFunc1 = () => {
  console.log('hello');
};
const innerFunc2 = () => {
  console.log('javascript');
};
const innerFunc3 = () => {
  console.log(); // 빈값은 undefined
};

 

이렇게 보면 innerFunc1, innerFunc2, innerFunc3을 호출할 때 콘솔 결과가 왜 그런지 알 수 있다. 

func처럼 함수를 만드는 함수를 고차 함수(high order function)라고 한다.

 

참고로 화살표 함수 문법에 따라 함수의 본문에서 바로 반환되는 값이 있으면 { return을 생략할 수 있다.

 

const func = (msg) => {
  return () => {
    console.log(msg);
  };
};

 

즉, 앞의 코드는 다음과 같이 바꿀 수 있다. 앞으로 화살표가 연이어 나오더라도 당황하지 말자 !

 

고차 함수에서 { 와 return 생략

const func = (msg) => () => {
  console.log(msg);
};

 

이렇게 길게 고차 함수를 설명한 이유가 있다. 이벤트 리스너를 연결하는 코드에서 0, 1, ..., 9까지의 숫자를

제외한 나머지 부분이 같기 때문이다. 고차 함수를 사용해 0부터 9까지 저장하는 부분은 매개변수로 만들고

다른 부분은 함수로 만든다.

 

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

 

앞으로 return을 생략할 수 있는 함수에서는 return을 생략하도록 하겠다.

 

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

 

그럼 이벤트 리스너 부분을 다음과 같이 수정한다.

 

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

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'));
…

 

이번에도 onClickNumber('숫자') 부분을 함수의 return 값으로 치환해 보면 이해할 수 있다.

 

참고로 고차 함수를 사용하지 않고도 중복을 제거할 수 있습니다. 앞의 코드에서 고차 함수를 사용한 이유는 onClickNumber 함수 내부의 다른 코드는 똑같은데 0~9라는 숫자만 다르기 때문이다.

숫자를 같게 만들 수 있다면 모든 코드가 똑같아지므로 쉽게 중복을 제거할 수 있다.

 

어떻게 숫자 부분을 같게 만들 수 있을까? 숫자들의 공통점을 찾으면 된다. 숫자는 모두 버튼 안에 들어 있는

문자이다. 버튼을 클릭할 때 버튼 내부의 문자를 event.target.textContent로 가져올 수 있다.

따라서 onClickNumber 함수의 코드를 같게 만들 수 있다.

 

…
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);
…

 

이렇게 쉽게 중복을 제거할 수 있는데 왜 어려운 고차 함수를 사용할까?

 

앞에서 설명한 것처럼 ‘모든 내부 코드가 같은’ 함수보다 ‘대부분 다 비슷한데 특정 부분만 다른’ 함수

실무에서는 더 많이 나오기 때문이다. 이럴 때는 고차 함수를 사용해 함수의 중복을 제거해야 한다.

연산자 버튼을 누를 때도 다음과 같이 고차 함수를 사용해 연산자를 저장할 수 있다.

고차 함수를 사용해야 하는 경우가 바로 나온다. 연산자를 operator 변수에 저장하면서 화면($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('*'));
…

 

 

 

<1분 퀴즈>

 

다음 코드의 console.log 결과를 맞혀 보세요.

 

const hof = (a) => (b) => (c) => { 
  return a + (b * c); 
}; 
const first = hof(3); 
const second = first(4); 
const third = second(5); 
console.log(third);

 

 


 

 

<중첩 if 문 줄이기>

 

: 지금까지 작성한 코드를 정리하면 다음과 같다.

 

<script>
  let numOne = '';
  let operator = '';
  let numTwo = '';
  const $operator = document.querySelector('#operator');
  const $result = document.querySelector('#result');
  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);
  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', () => {});
  document.querySelector('#clear').addEventListener('click', () => {});
</script>

 

 

여기서 HTML을 실행해 보면 버그가 발생한다. 두 번째 숫자(numTwo)를 클릭하면 첫 번째 숫자와

합쳐지는 버그다. 3 + 4를 수행해 보자.

 

3 + 4 수행 결과

 

4를 누르면 결과창에 4만 뜨는 것이 아니라 34가 뜬다.

따라서 numTwo에 값을 저장하기 전에 화면을 비우는 작업이 필요하다. 다음과 같이 코드를 수정하자.

 

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

 

이제 numTwo에 값이 없을 때 numTwo를 입력하는 상황이 되면 먼저 화면을 비우고 값을 입력한다.

코드를 보면 if 문 내부에 다시 if 문이 존재한다. 이런 상황을 if 문이 중첩됐다고 표현한다.

분기점이 많은 코드를 작성하다 보면 if 문을 중첩해서 쓰는 경우가 한다.

 

실무에서는 다음과 같은 경우를 흔히 볼 수 있다고 한다.

 

if (조건A) {
  if (조건B) {
    if (조건C) {
    }
  } else {
    if (조건D) {
      // 나는 어느 때 실행되지?
      if (조건E) {
      } else {
        // 나는?
      }
      // 나는?
    } else {
    }
  }
}

 

if 문이 여러 번 중첩될수록 코드는 읽기가 점점 더 어려워진다. 이 같은 상황은 피하는 게 좋다.

실제로 대부분의 if 문은 순서도만 살짝 바꿔도 중첩을 피할 수 있다.

 

기존 코드의 중첩을 제거해 보겠다. 중첩을 제거하는 방법은 다음과 같다.

 

1. if 문 다음에 나오는 공통된 절차를 각 분기점 내부에 넣는다

2. 분기점에서 짧은 절차부터 실행하게 if 문을 작성한다.

3. 짧은 절차가 끝나면 return(함수 내부의 경우)이나 break(for 문 내부의 경우)로 중단한다.

4. else를 제거한다(이때 중첩 하나가 제거된다).

5. 다음 중첩된 분기점이 나오면 1~4의 과정을 반복한다.

 

첫 번째 단계로 onClickNumber 함수에서 if 문과 상관없이 공통적으로 실행되는 부분은 $result.value += event.target.textContent; 이다. 이 부분을 각 분기점 안에 넣는다.

 

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

 

 

두 번째 단계로 어떤 분기점의 절차가 더 짧은지 확인한다. 

operator가 없으면 절차가 더 짧으므로 먼저 작성한다. 이때 조건문은 operator에서 !operator가 된다.

 

const onClickNumber = (event) => {
  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;
  }
};

 

세 번째 단계로 !operator일 때의 절차가 마무리되면 return으로 함수를 종료한다. 네 번째 단계로

 return 아랫부분은 무조건 operator일 때만 실행되므로 else 문으로 감쌀 필요가 없다.

 

const onClickNumber = (event) => {
  if (!operator) {
    numOne += event.target.textContent;
    $result.value += event.target.textContent;
    return;
  }
  // 이 아래로는 operator가 존재하는 경우에만 실행됨
  if (!numTwo) {
    $result.value = '';
  }
  numTwo += event.target.textContent;
  $result.value += event.target.textContent;
};

 

이렇게 중첩된 if 문이 사라졌다. else 문이 사라져서 읽기 어렵다고 생각할 수도 있다.

하지만 익숙해지면 이렇게 작성하는 것이 읽기 편하다. 짧은 절차가 먼저 나온다고 생각하면 된다.

 

사실 코드에서 몇 단계까지 중첩을 허용할지에 대한 정답은 없다. 다만, 대부분은 중첩 3단계부터 코드가

읽기 어려워진다고 생각하므로 3단계까지 가지 않기를 추천한다.

 

 

 

 

<1분 퀴즈>

 

다음 if 문의 중첩을 줄여 보세요.

function test() { 
  let result = ''; 
  if (a) { 
    if (!b) { 
      result = 'c'; } 
    } 
  } else { 
    result = 'a'; 
  } 
  result += 'b'; 
  return result; 
}

 

 

 


 

 

 

<결과 계산하기>

 

: 이제 #calculate 태그에 이벤트 리스너를 달아 결과를 출력해보자. 다음 순서도에 따라 코드를 작성하겠다.

 

= 버튼 클릭 부분 순서도

 

‘숫자 1과 숫자 2에 연산자를 적용해 계산한다’라는 절차를 어떻게 구현해야 할지 고민된다.

현재 operator 변수는 +, -, *, / 같은 문자열을 저장하고 있다. 이런 문자열을 어떻게 연산자로 바꿀까?

 

결론부터 말하면, 문자열은 자료형 중 하나로 연산자와는 다르므로 문자열을 연산자로 바꿀 수는 없다.

하지만 문자열에 따라 다른 연산자를 사용하도록 조건문을 사용해 분기 처리를 할 수 있다.

 

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('숫자를 먼저 입력하세요.');
  }
});

 

switch 문 대신 if 문을 사용해도 된다. 다만, 조건에 해당하는 변수가 operator로 계속 같으므로 switch 문이

조금 더 깔끔해 보인다.

 

더할 때 parseInt를 사용하는 점이 조금 특이하다. 처음에 numOne numTwo를 문자열로 저장했다는

사실을 기억하자. 문자열끼리 더하면 원하는 결괏값을 얻을 수 없다. 

'34' + '26'을 하면 60이 아니라 '3426'이 되기 때문이다. 그래서 더할 때는 각각을 parseInt 함수를 통해

숫자로 바꾼 후 더한다.

 

빼기, 곱하기, 나누기를 할 때는 숫자로 바꿀 필요가 없다. 이 연산자들을 사용할 때는 자동으로 문자열을

숫자로 바꾼 후에 계산한다.

 

NOTE eval 사용하기

앞에서 문자열을 연산자로 바꿀 수 없다고 했지만, 문자열을 자바스크립트 코드처럼 실행하는 방법이 있다.

바로 eval 함수를 사용하는 방법이다.

 

eval 함수 사용 예시

 

eval('1 + 2'); // 3
document.querySelector('#calculate').addEventListener('click', () => {
  if (numTwo) {
    $result.value = eval(numOne + operator + numTwo);
  } else {
    alert('숫자를 먼저 입력하세요.');
  }
});

 

예를 들어 numOne 5, operator -, numTwo 2인 경우 numOne + operator + numTwo '5-2'라는

문자열이 된다. 이 문자열이 eval 함수로 실행되면 결과로 3이 반환된다.

 

코드가 간단해져서 편리한 기능이지만, eval 함수를 남용하면 해커가 악용할 가능성이 있다. 

eval 함수에 문자열을 입력하면 그대로 실행되므로 해커가 이를 통해 우리의 프로그램에 위험한 코드를

실행할 수도 있다.

따라서 실무에서는 eval 함수의 사용을 피하는 것이 보안상 안전하다.

 

 

 

<1분 퀴즈>

 

(가) 부분에 어떤 문자열을 넣으면 alert 창이 실행될까요?

단, eval의 실행을 막은 사이트들도 있으므로 calculator.html을 띄운 창의 콘솔에서 실행해야 합니다.

 

const str = (가); 
eval('a'+ str +'t("eval은 위험해요")');

 

 

 


 

 

<계산기 초기화 하기>

 

: 마지막으로, #clear 태그(C 버튼)가 클릭되는 것을 감지할 이벤트 리스너를 작성하겠다.

모든 것을 초기화하면 된다. 초기화한다는 것은 처음 상태로 되돌린다는 것을 의미한다. 변수와 함수 모두

초기 상태로 설정하면 된다.

 

document.querySelector('#clear').addEventListener('click', () => {
  numOne = '';
  operator = '';
  numTwo = '';
  $operator.value = '';
  $result.value = '';
});

 

#clear 태그까지 합쳐 완성된 코드는 다음과 같다. 완성된 코드를 실행해 보자.

지금 구현된 코드로는 단 한 번만 계산할 수 있다. 한 번 계산한 후 다른 계산을 하려면 C를 눌러서 초기화한

후 다시 계산해야 한다. 

 

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', () => {
  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('숫자를 먼저 입력하세요.');
  }
});
document.querySelector('#clear').addEventListener('click', () => {
  numOne = '';
  operator = '';
  numTwo = '';
  $operator.value = '';
  $result.value = '';
});

 

 


 

 

<마무리 요약>

 

이 장에서 배운 내용을 정리해 보겠다.

 

1 고차 함수 사용하기

: 함수를 호출할 때마다 반환 함수를 생성하는 함수를 고차 함수(high order function)라고 한다.

 

const func = () => {
  return () => {
    console.log('hello');
  };
};

 

반환된 함수는 다른 변수에 저장할 수 있고, 그 변수에 저장된 함수를 다시 호출할 수 있다.

 

const innerFunc = func();
innerFunc(); // hello

 

반환하는 값을 바꾸고 싶을 때는 매개변수를 사용한다.

 

const func = (msg) => {
    return () => {
        console.log(msg);
    };
};

 

화살표 함수 문법에 따라 함수의 본문에서 바로 반환되는 값이 있으면 {  return을 생략할 수 있다.

 

const func = (msg) => () => {
  console.log(msg);
};

 

 

2 if 문 중첩 제거하기

: if 문이 중첩되면 코드를 파악하기 어렵다. 다음과 같은 방법으로 if 문의 중첩을 제거한다.

 

1) 공통된 절차를 각 분기점 내부에 넣는다.

2) 분기점에서 짧은 절차부터 실행하게 if 문을 작성한다.

3) 짧은 절차가 끝나면 return(함수 내부의 경우)이나 break(for 문 내부의 경우)로 중단한다.

4) else를 제거한다(이때 중첩 하나가 제거된다).

5) 다음 중첩된 분기점이 나올 때 1~4의 과정을 반복한다.

 

 


 

 

<Self Check 연이어 계산하기>

 

앞에서 만든 계산기는 계산을 단 한 번만 할 수 있다. 한 번 계산한 후에는 C 버튼을 눌러 초기화하지

않는 이상 제대로 작동하지 않는다. 1 + 2 + 4처럼 계산하고 싶다면 코드를 어떻게 수정하면 될까?

 

힌트 1 + 2 + 4를 실제로 계산기에 입력하면 버튼을 1, +, 2, =, +, 4, = 순으로 눌러야 한다.

두 번째 +를 눌렀을 때 numOne, operator, numTwo 변수가 어떤 값을 갖고 있어야 할지 생각해 보자.

 

 

반응형