[JS] 모던 자바스크립트 Deep Dive 스터디 8장 제어문 프로젝트에 어떻게 적용할지 알아보기 #해달

2024. 1. 19. 05:05_Web/JavaScript

728x90

 

 

https://dingx2-story.tistory.com/124

 

[JS] 모던 자바스크립트 Deep Dive 스터디 7장 연산자를 배우며 프로젝트에 어떻게 적용할지 알아보

자바스크립트로 개발하다보면 정말 수상한 오류가 발생한다. 이는 대부분 연산에서 발생하는 오류이며 연산자의 개념을 바로잡고가야 치명적인 오류에도 빠른 대처가 가능하다. 필자는 그동안

dingx2-story.tistory.com

앞선 글과 연결됩니다.

 

 

#제어문 #반복문 #블록문

 

 

 


08장

제어문

 

 

제어문은 (control flow statement)은 조건에 따라 코드 블록을 실행(조건문)하거나

반복 실행(반복분)할 때 사용하며 일반적으로 코드는 위에서 아래 방향으로 순차적으로 실행된다.

 

제어문을 이용하여 코드 실행 흐름을 인위적으로 변경할 수 있다. 

 

 

 

8.1. 블록문 (block statement/compound statement)

 

0개 이상의 문을 중괄호로 묶은 것, 코드 블록 또는 블록이라고 부른다. 자바스크립트는 블록문을 하나의 실행단위로 취급하며, 단독으로도 사용가능하지만 일반적으로 제어문, 함수를 정의할 때 사용한다.

 

블록문이 사용되는 다양한 예제이며 세미콜론을 붙이는 것이 일반적이다.

그렇다면 세미콜론을 붙이는 것과 안 붙이는 것은 개발자의 자유이다. 하지만 붙이는게 좋은 이유가 있다.

https://www.youtube.com/watch?v=hJjYvVOEO7s

 

 

 

const x =1
const y = x

[0,1,2].forEach(num=>{
    console.log(num)
})

 

이렇게 작성된 코드가 세미콜론이 없어서 x에서 끝나는 것이 아니라 x[0,1,2] 이런식으로 실행되어 x에서 꺼내려는 모습이 되어 에러가 발생한다. 따라서 세미콜론을 붙이든, 한 줄에 한 명령만 내리는 것이 맞다.

 

 

 

그렇다면 좋은 코드는 무엇일까? 에어비앤비 컨벤션에 따르면

16.1 여러 줄의 블록에는 중괄호를 사용한다. eslint: nonblock-statement-body-position

// bad
if (test)
  return false;

// good
if (test) return false;

// good
if (test) {
  return false;
}

// bad
function foo() { return false; }

// good
function bar() {
  return false;
}

 

 

또한 클린코드를 위해서 indent depth(들여쓰기)는 2칸 이상을 들어가는 것은 좋지않다.

 

 

 

 

 

8.2. 조건문 (conditional statement)

 

조건문은 주어진 조건식의 평과 결과에 따라 코드 블록 (블록문)의 실행을 결정한다.

조건식은 불리언 값으로 평가될 수 있는 표현식이다.

 

자바스크립트 if ...else 문과 switch 문으로 두 가지 조건문을 제공한다.

 

if ... else 문

 

if (조건식){

// 참이면 여기가 실행

} else{

// 거짓이면 여기가 실행

}

function evalNum () {
  const x = 21;

  if (x % 2) {
    console.log('홀수입니다.');
    return;
  }

  if (x % 4) {
    console.log('짝수입니다.');
    return;
  }

  console.log('4의 배수입니다.');
}

evalNum();
console.log('블록문 바깥');

 

 

if 문의 조건식은 불리언 값으로 평가되어야 한다. 만약 if 문의 조건식이 불리언 값이 아닌 값으로 평가되면 자바스크립트 엔진에 의해 암묵적으로 불리언 값으로 강제 변환되어 실행할 코드 블록을 결정한다.

 

앞선 7장에서 삼항연산자를 사용해보았다. 그러나 삼항연산자도 자주 쓰면 편하겠지만 에어비앤비 코드 컨벤션에 따르면 if 문을 자주 쓰는 것을 권장하고 삼항연산자는 지양한다.

 

// bad
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;

// good
const foo = a || b;
const bar = !!c;
const baz = !c;

 

 

또한 최대한 조건문을 축약해서 적어야한다.

// bad
if (name !== '') {
  // ...stuff...
}

// good
if (name) {
  // ...stuff...
}

// bad
if (collection.length > 0) {
  // ...stuff...
}

// good
if (collection.length) {
  // ...stuff...
}

 

 

 

switch 문

 

switch 문은 주어진 표현식을 평가하여 그 값과 일치하는 표현식을 갖는 case 문으로 실행 흐름을 옮긴다.

문의 표현식과 표현식 1이 일치하면 실행되는 문이며, 도서에 나와있듯이 폴스루(fall through)를 통해 break가 없으면 계속 아래로 실행됨을 주의해야한다.

 

 

15.5 렉시컬 선언 (e.g. let, const, function, and class)을 포함하는 case와 default 구문 안에 블록을 만들 때는 괄호를 사용하세요. eslint: no-case-declarations

왜? 렉시컬 선언은 switch 블록 전체에서 접근할 수 있지만, 할당된 경우에만 초기화됩니다. case에 다다랐을 때 말이죠. 이것은 여러 case 구문이 같은 것을 정의하려 할 때 문제를 일으킵니다.

// bad
switch (foo) {
  case 1:
    let x = 1;
    break;
  case 2:
    const y = 2;
    break;
  case 3:
    function f() {
      // ...
    }
    break;
  default:
    class C {}
}

// good
switch (foo) {
  case 1: {
    let x = 1;
    break;
  }
  case 2: {
    const y = 2;
    break;
  }
  case 3: {
    function f() {
      // ...
    }
    break;
  }
  case 4:
    bar();
    break;
  default: {
    class C {}
  }
}

 

 

해당 코드를 브라우저에 실행하여 1과 2에서 break의 유무에 따라 어떻게 실행되는지 찾아보자

let index = 2
switch(index){
    case index===1:
        console.log('1입니다');
    case index===2:
        console.log('2입니다.');
        break;
    default:
        console.log('다른 숫자입니다.');
}

 

 

 

 

 

 

 

사실 이 코드는 이상한 코드이다. case 뒤에 표현식에서 index===2는 true가 반환되기 때문이다. index의 변경값을 확인하고싶다면 아래의 코드를 실행하여 index를 바꿔보자

 

let index = 1
let isValid = true

switch(index){
    case 1:
        console.log('1입니다');
        break;
    case index === 2:
        console.log('2입니다.');
        break;
    default:
        console.log(`${index}다른 숫자입니다.`);
}

 

 

이외에도 isValid를 추가한 유효성 검사에도 오류가 발생할 수 있다. &&의 위치만으로도 다른 결과가 발생한다.

 

 

 

얄코님의 코드인 아래 코드를 보면 시야를 넓힐 수 있다. 

 

// 💡 참고: 객체를 사용한 방법
const direction = 'north'

const directionKor = {
  north: '북',
  south: '남',
  east: '동',
  west: '서'
}[direction] ?? '무효'

console.log(directionKor);
const month = 1;
let season = '';

switch (month) {
  case 1: case 2: case 3:
    season = '1분기'; break;

  case 4: case 5: case 6:
    season = '2분기'; break;

  case 7: case 8: case 9:
    season = '3분기'; break;

  case 10: case 11: case 12:
    season = '4분기'; break;

  default: 
    season = '잘못된 월입니다.';
}

console.log(season);
const startMonth = 1;
let holidays = '분기 내 휴일:';

switch (startMonth) {
  case 1:
    holidays += ' 설날';
  case 2:
  case 3:
    holidays += ' 3•1절';
    break;

  case 4:
  case 5:
    holidays += ' 어린이날';
  case 6:
    holidays += ' 현충일';
    break;

  case 7:
  case 8:
    holidays += ' 광복절';
  case 9:
    holidays += ' 추석';
    break;

  case 10:
    holidays += ' 한글날';
  case 11:
  case 12:
    holidays += ' 크리스마스';
    break;

  default: 
    holidays = '잘못된 월입니다.';
}

console.log(holidays);

 

 

 

function check(index = 1) {
    let answer = null;
    if (index) {
        console.log(`${index}번째 페이지입니다`);
    }
}

check(10);

 

그렇다면 7장의 코드에서 어떤 치명적인 오류가 아직 있을까? 문자열오류는 해결하였다. 그리고 반환값에서 에러가 발생할 여지도 삭제하였다. 

 

 

다음을 통해 해결할 수 있다.

 

 

정답은 다음과 같다.

더보기

배열은 0부터 시작하지만 자바스크립트에선 false이다. 따라서 0번째 index의 값을 사용하지 않는다. 다른 프로젝트에서 배열로 편지를 읽는데 첫번째 편지(0번째 index)만 사라지는 일이 있었다.

 

 

 

 

 

이제 아래처럼 수정을 한다면 어떤 문제가 발생할 수 있을까? -1번째에 접근가능하다. 이처럼 조금씩 조건을 추가해야한다.

function check(index = 1) {
    let answer = null;
    if (index !==  undefined || index === 0) {
        console.log(`${index}번째 페이지입니다`);
    }
}
check(-1);
check(0);
check(2);

 

 

아예 로직으로 판단한다면 아래코드가 더 보기엔 좋다.

function check(index = 1) {
    let answer = null;
    if (index>=0) {
        console.log(`${index}번째 페이지입니다`);
    }
}
check(-1);
check(0);
check(2);

 

 

 

 

8.3. 반복문 (loop statement)

반복문은 조건식의 평가 결과가 참인 경우 코드 블록을 실행한다.

for, while, do...while 문을 제공한다. forEach 메서드, map과 같은 반복문도 알아보자.

 

 

for (let i = 0; i < 5;) {
  console.log(i++); // ++i로 바꿔볼 것
}

for 문은 매우매우 중요하다. 코드 블록이 먼저 실행되는 것을 주의하자.

 

for (변수 선언문 또는 할당문; 조건식; 증감식){
참인 경우 반복실행될 문;
}

for (var i=0; i<2; i ++){
    console.log(i);}



변수 선언문, 조건식, 증감식은 모두 옵션이기 때문에 안 적어도 된다. 따라서 무한루프를 돌리고 싶다면 for(;;){ ... } 식으로 사용하면된다.

 

for (let i = 10; i >= 0; i-= 2) {
  console.log(i);
}

 

중첩에서 사용해도 되며, 둘이상의 변수도 사용할 수 있다. 아래는 얄코님의 코드이다.

for (let i = 1; i <= 9; i++) {
  for (let j = 1; j <= 9; j++) {
    console.log(`${i} * ${j} = ${i * j}`);
  }
}
for (let x = 0, y = 10; x <= y; x++, y--) {
  console.log(x, y);
}

 

 

 

 

while

while문은 주어진 조건식의 평가 결과가 참이면 코드 블록에서 계속 반복 실행한다.

for 과 while 의 차이점은 반복횟수이다.

 

for은 애초에 설계할 때부터 5번, 10번 실행할 것을 정하지만

while문은 -1이 입력될 때까지 실행 이런 경우에 사용하면 좋다.

 

do while() 같은경우 일단 실행하고 조건식을 표현한다.

따라서 이런 경우, 어떤 값을 입력받고 판단할 때 사용하면 좋다.

 

break문은 코드 블록을 탈출하므로, 콘솔창에서 무한렌더링으로 console.log로 도배되는 현상이 발생하지 않게 적절하게 break로 반복횟수를 설정하는게 안전하다. 실제로 채팅 기능을 위해 웹소켓을 다루면서 3초 안에 채팅방 2만여개가 만들어지면서 백엔드 서버가 폭파된 적이 있었다. (갑자기 서버가 터져? 타이밍이 너무 유력했다.)

 

 

오늘 처음봐서 특이한건 레이블 문(label statement)이란 식별자가 붙은 문이다. 실행 순서를 제어하는데 사용한다. for 문 외부로 탈출할 때 유용하지만 그 밖의 경우에서는 가독성이 나빠져서 권장하지 않는다. switch문에서도 사용할 수 있어 불필요한 반복을 피할 수 있다.

 

 

 

 

 

continue 문


continue문은 반복문의 코드 블록 실행을 현 지점에서 중단하고 증감식으로 실행 흐름을 이동한다. 따라서 break 문처럼 반복문을 탈출하지 않는다.

 

 

 

 

그렇다면 좋은 반복문은 무엇일까? 우리는 배열을 다룰 때 반복문을 빼놓을 수 없다. forEach, map, for문 도대체 어떤게 좋은 반복문이며 우리는 어떤 반복문을 주로 써야하는 걸까? 자바스크립트와 관습화가 되어버린 에어비엔비 컨벤션에서 찾아봤다.

 

https://github.com/parksb/javascript-style-guide

 

GitHub - parksb/javascript-style-guide: Airbnb JavaScript 스타일 가이드

Airbnb JavaScript 스타일 가이드. Contribute to parksb/javascript-style-guide development by creating an account on GitHub.

github.com

 

이터레이터와 제너레이터 (Iterators and Generators)

 

  • 11.1 이터레이터를 사용하지 마세요. for-in이나 for-of같은 루프 대신 자바스크립트의 고급함수를 사용하세요. eslint: no-iterator no-restricted-syntax배열을 이터레이트할 때 map() / every() / filter() / find() / findIndex() / reduce() / some() / ... 를 사용하세요. 배열을 생성할 때는 Object.keys() / Object.values() / Object.entries()를 사용해서 모든 객체를 이터레이트 할 수 있습니다.
  • 왜? 고급함수는 불변 규칙을 적용합니다. 사이드 이펙트에 대해 추측하는 것보다 값을 반환하는 순수 함수를 다루는 것이 더 간단합니다.

 

또한 for 문에서 i를 변경할 수 있기 때문에, for 문을 사용하는 것을 지양해야한다.

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// ⚠️ 변수(i)를 사용하므로 위험요소 존재
for (let i = 0; i < numbers.length; i++) {
  // 이곳에 i를 변경하는 코드가 들어간다면...
  console.log(numbers[i]);
}

 

 

따라서 for 말고 forEach, map, reduce을 사용하자. 아래는 에어비엔비 컨벤션이다.

const numbers = [1, 2, 3, 4, 5];

// bad
let sum = 0;
for (let num of numbers) {
  sum += num;
}
sum === 15;

// good
let sum = 0;
numbers.forEach((num) => {
  sum += num;
});
sum === 15;

// best (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;

// bad
const increasedByOne = [];
for (let i = 0; i < numbers.length; i++) {
  increasedByOne.push(numbers[i] + 1);
}

// good
const increasedByOne = [];
numbers.forEach((num) => {
  increasedByOne.push(num + 1);
});

// best (keeping it functional)
const increasedByOne = numbers.map(num => num + 1);

 

그렇다면 forEach, map, reduce 이 셋의 차이는 어떻게 될까?

 

forEach : 원본배열을 직접 변경, 반환값이 없다.

const numbers = [1, 2, 3];
numbers.forEach((number) => console.log(number));
// 출력: 1, 2, 3

 

map : 새로운 배열 생성하고 반환

const numbers = [1, 2, 3];
const squaredNumbers = numbers.map((number) => number * number);
// squaredNumbers는 [1, 4, 9]

 

reduce : 각 요소의 값을 하나로 축소하거나 누적

const numbers = [1, 2, 3];
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
// sum은 6

 

 

각각의 필요성이 모두 다르다. 상황에 따라 사용하기 !