ELITE HACKER Bootcamp 4th/2주차

[2주차 TIL] KnockOn Bootcamp Javascript Part 2

푸림님 2025. 4. 12. 00:47

모던 JS 문법

  • 모던 JS 문법은 ES6(ES2015) 이후 도입된 기능들로, 개발자 경험을 개선하고 코드를 간결하게 만듭니다.
  • Arrow Function, Spread 구문, Optional Chaining 외에도 Destructuring, Template Literals, Default Parameters 자주 사용되는 문법을 추가로 다루어보겠습니다.

Arrow Function (화살표 함수)
  - Arrow Function은 함수를 간결하게 정의하는 방법으로, function 키워드 대신 =>를 사용합니다. this 바인딩이 렉시컬(정적) 스코프를 따르는 점이 특징입니다.

 

기본사용

// 일반 함수
function add(a, b) {
    return a + b;
}

// Arrow Function
const addArrow = (a, b) => a + b; // 한 줄일 경우 중괄호와 return 생략 가능

console.log(add(3, 5));      // 8
console.log(addArrow(3, 5)); // 8

 

매개변수가 하나일 경우

 - 괄호 생략 가능

const square = x => x * x;
console.log(square(4)); // 16

 

객체 반환

 -객체를 반환할 때는 소괄호로 감싸야 함

const getPerson = name => ({ name: name, age: 30 });
console.log(getPerson("홍길동")); // { name: "홍길동", age: 30 }

 

this 바인딩 차이

 - Arrow Functiuon은 this를 정의된 위치(렉시컬 스코프)에서 가져옵니다.

const obj = {
    name: "김철수",
    sayHello: function() {
        setTimeout(function() {
            console.log(this.name); // 일반 함수: this는 setTimeout의 this (undefined)
        }, 1000);

        setTimeout(() => {
            console.log(this.name); // Arrow Function: this는 obj (김철수)
        }, 1000);
    }
};
obj.sayHello();
// 출력:
// undefined
// 김철수

 

활용 예시

 - 배열 메소드와 함께

const numbers = [1, 2, 3, 4];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8]

 

 

Spread 구문(전개 연산자, ...)

 - Spread 구문은 배열이나 객체의 요소를 확장하거나 복사할 때 사용됩니다.

 

배열에서 사용

// 배열 복사
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // 얕은 복사
console.log(arr2); // [1, 2, 3]

// 배열 병합
const arr3 = [...arr1, 4, 5];
console.log(arr3); // [1, 2, 3, 4, 5]

// 배열 요소 전달
const sum = (a, b, c) => a + b + c;
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

 

객체에서 사용

// 객체 복사
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1 }; // 얕은 복사
console.log(obj2); // { a: 1, b: 2 }

// 객체 병합
const obj3 = { ...obj1, c: 3 };
console.log(obj3); // { a: 1, b: 2, c: 3 }

// 기존 속성 덮어쓰기
const obj4 = { ...obj1, a: 10 };
console.log(obj4); // { a: 10, b: 2 }

 

활용 예시

 - 함수 인자

const user = { name: "홍길동", age: 25 };
const updatedUser = { ...user, age: 26, city: "서울" };
console.log(updatedUser); // { name: "홍길동", age: 26, city: "서울" }

 

 

Optional Chaining(?.)

 - Optional Chaining은 객체 속성에 안전하게 접근할 때 사용됩니다. 속성이 존재하지 않을 경우 undefined를 반환하며, 오류를 방지합니다.

 

기본 사용

const user = {
    name: "김철수",
    address: { city: "부산" }
};

console.log(user.address?.city);    // "부산"
console.log(user.phone?.number);    // undefined (오류 없이 접근)

 

중첩된 객체

const data = {
    user: {
        profile: { age: 30 }
    }
};

console.log(data.user?.profile?.age);      // 30
console.log(data.user?.settings?.theme);   // undefined

 

함수 호출에서 사용

const obj = {
    sayHello: () => "안녕!"
};

console.log(obj.sayHello?.());     // "안녕!"
console.log(obj.sayGoodbye?.());   // undefined

 

활용 예시

 - API 데이터 처리

const response = {
    user: { name: "홍길동" }
};

const userName = response.user?.name ?? "익명"; // Optional Chaining + Nullish Coalescing
console.log(userName); // "홍길동"

 

 

Destructuring(구조 분해 할당)

 - 객체나 배열에서 값을 추출해 변수로 바로 할당합니다.

 

객체 구조 분해

const person = { name: "홍길동", age: 22, city: "대구" };

// 기존 방식
// const name = person.name;
// const age = person.age;

// 구조 분해
const { name, age } = person;
console.log(name, age); // "홍길동" 22

// 별칭 사용
const { name: userName, city: location } = person;
console.log(userName, location); // "홍길동" "대구"

 

배열 구조 분해

const fruits = ["사과", "바나나", "포도"];

// 기존 방식
// const first = fruits[0];
// const second = fruits[1];

// 구조 분해
const [first, second] = fruits;
console.log(first, second); // "사과" "바나나"

// 일부 요소 건너뛰기
const [, , third] = fruits;
console.log(third); // "포도"

 

활용 예시

 - 함수 매개변수

const printUser = ({ name, age }) => {
    console.log(`${name}은 ${age}살입니다.`);
};

const user = { name: "김철수", age: 28 };
printUser(user); // "김철수은 28살입니다."

 

 

Template Literals(템플릿 리터럴)

- 백틱(`)을 사용해 문자열을 더 간결하게 작성합니다. 변수나 표현식을 ${}로 삽입 가능합니다.

const name = "홍길동";
const age = 25;

// 기존 방식
const greeting = "안녕, " + name + "! 나이는 " + age + "살이야.";

// 템플릿 리터럴
const greetingModern = `안녕, ${name}! 나이는 ${age}살이야.`;
console.log(greetingModern); // "안녕, 홍길동! 나이는 25살이야."

// 표현식 사용
console.log(`2 + 3 = ${2 + 3}`); // "2 + 3 = 5"

 

활용 예시

 - HTML 생성

const user = { name: "이영희", age: 22 };
const html = `
    <div>
        <h1>${user.name}</h1>
        <p>나이: ${user.age}</p>
    </div>
`;
console.log(html);

 

 

Default Parameters(기본 매개변수)

 - 함수 매개변수에 기본값을 설정합니다.

function greet(name = "익명", greeting = "안녕") {
    return `${greeting}, ${name}!`;
}

console.log(greet());              // "안녕, 익명!"
console.log(greet("홍길동"));      // "안녕, 홍길동!"
console.log(greet("김철수", "hi")); // "hi, 김철수!"

 


 

비동기 관련 문법

  • 자바스크립트는 비동기 작업(예: 네트워크 요청, 파일 읽기, 타이머)을 처리하기 위해 Callback, Promise, async/await를 사용합니다. 
  • Callback은 오래된 방식으로, 현재는 Promise와 async/await가 주로 사용됩니다.

Callback (콜백 함수)

 - 비동기 작업이 완료된 후 실행할 함수를 전달하는 방식입니다. 하지만 "콜백 지옥(Callback Hell)" 문제가 발생할 수 있습니다.

 

예시

 - 콜백 지옥

setTimeout(() => {
    console.log("1단계 완료");
    setTimeout(() => {
        console.log("2단계 완료");
        setTimeout(() => {
            console.log("3단계 완료");
        }, 1000);
    }, 1000);
}, 1000);
// 출력:
// 1초 후: "1단계 완료"
// 2초 후: "2단계 완료"
// 3초 후: "3단계 완료"

 

Promise
 - Promise는 비동기 작업의 성공(resolve) 또는 실패(reject)를 처리하는 객체입니다. 콜백 지옥을 해결하는 데 유용합니다.

 

기본 사용

const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = true; // 가정: 작업 성공
        if (success) {
            resolve("작업 성공!");
        } else {
            reject("작업 실패!");
        }
    }, 2000);
});

myPromise
    .then(result => console.log(result)) // 성공 시 실행
    .catch(error => console.error(error)); // 실패 시 실행
// 2초 후: "작업 성공!"

 

Promise 체이닝

const step1 = () => new Promise(resolve => {
    setTimeout(() => resolve("1단계 완료"), 1000);
});

const step2 = () => new Promise(resolve => {
    setTimeout(() => resolve("2단계 완료"), 1000);
});

const step3 = () => new Promise(resolve => {
    setTimeout(() => resolve("3단계 완료"), 1000);
});

step1()
    .then(result => {
        console.log(result);
        return step2();
    })
    .then(result => {
        console.log(result);
        return step3();
    })
    .then(result => console.log(result))
    .catch(error => console.error(error));
// 출력:
// 1초 후: "1단계 완료"
// 2초 후: "2단계 완료"
// 3초 후: "3단계 완료"

 

Promise.all

 - 여러 Promise를 병렬로 실행합니다

Promise.all([step1(), step2(), step3()])
    .then(results => console.log(results)) // 모든 Promise가 완료된 후 실행
    .catch(error => console.error(error));
// 출력: ["1단계 완료", "2단계 완료", "3단계 완료"]

 

async/await
 - async/await는 Promise를 더 간결하고 동기적으로 보이게 작성하는 문법입니다. async 함수는 항상 Promise를 반환합니다.

 

기본 사용

async function fetchData() {
    try {
        const result = await new Promise(resolve => {
            setTimeout(() => resolve("데이터 가져오기 성공!"), 2000);
        });
        console.log(result);
    } catch (error) {
        console.error("에러:", error);
    }
}

fetchData(); // 2초 후: "데이터 가져오기 성공!"

 

Promise 체이닝 대체

async function runSteps() {
    try {
        const result1 = await step1();
        console.log(result1);
        const result2 = await step2();
        console.log(result2);
        const result3 = await step3();
        console.log(result3);
    } catch (error) {
        console.error(error);
    }
}

runSteps();
// 출력:
// 1초 후: "1단계 완료"
// 2초 후: "2단계 완료"
// 3초 후: "3단계 완료"

 

병렬 실행 (Promise.all과 함께)

async function runStepsParallel() {
    try {
        const results = await Promise.all([step1(), step2(), step3()]);
        console.log(results);
    } catch (error) {
        console.error(error);
    }
}

runStepsParallel();
// 출력: ["1단계 완료", "2단계 완료", "3단계 완료"]

 

요약

  • 모던 JS 문법
     1. Arrow Function: 간결한 함수 정의, this 바인딩이 렉시컬 스코프
     2. Spread 구문: 배열/객체 복사 및 병합
     3. Optional Chaining: 안전한 속성 접근
     4. Destructuring: 객체/배열에서 값 추출
     5. Template Literals: 문자열 내 변수 삽입
     6. Default Parameters: 함수 매개변수 기본값 설정
  • 비동기 관련 문법
     1. Callback: 비동기 작업의 기본, 콜백 지옥 문제
     2. Promise: 성공/실패 처리, 체이닝 가능
     3. async/await: Promise를 동기적으로 보이게 작성, 에러 처리를 try/catch로 관리
     4. Promise.all: 여러 비동기 작업 병렬 실행

 


코드 이해해보기

  • 첫번째 코드
// 변수 선언: let을 사용하여 재할당 가능한 문자열 변수 message를 선언하고 "Hello, World!"로 초기화
let message = "Hello, World!";

// 상수 선언: const를 사용하여 재할당 불가능한 상수 pi를 선언하고 3.14로 초기화
const pi = 3.14;

// 변수 선언: let을 사용하여 재할당 가능한 불리언 변수 isActive를 선언하고 true로 초기화
let isActive = true;

// 객체 선언: let을 사용하여 재할당 가능한 객체 변수 user를 선언
// 객체는 name과 age 속성을 가짐
let user = {
    name: "Hong Gil-dong", // 속성: name은 "Hong Gil-dong" 문자열
    age: 25                // 속성: age는 25 숫자
};

// 배열 선언: let을 사용하여 재할당 가능한 배열 변수 colors를 선언
// 배열은 "red", "green", "blue" 문자열 요소를 포함
let colors = ["red", "green", "blue"];

// 함수 정의: greet라는 이름의 함수를 정의, name 매개변수를 받음
function greet(name) {
    // 콘솔에 "Hello, "와 name, "!"를 연결한 문자열 출력
    console.log("Hello, " + name + "!");
}

// 함수 호출: greet 함수를 호출하며 "Anna"를 인자로 전달
// 콘솔에 "Hello, Anna!"가 출력됨
greet("Anna");

 

  • 두번째 코드
// 객체 선언: let을 사용하여 재할당 가능한 객체 변수 student를 선언
let student = {
    name: "Kim Yoon-sung", // 속성: name은 "Kim Yoon-sung" 문자열
    major: "Computer Science", // 속성: major는 "Computer Science" 문자열
    // 메소드 정의: getIntroduction이라는 함수를 속성으로 정의
    getIntroduction: function() {
        // 콘솔에 "My name is ", this.name, " and I study ", this.major, "."를 연결한 문자열 출력
        // this는 student 객체를 참조하므로 this.name은 "Kim Yoon-sung", this.major는 "Computer Science"
        console.log("My name is " + this.name + " and I study " + this.major + ".");
    }
};

// 메소드 호출: student 객체의 getIntroduction 메소드를 호출
// 콘솔에 "My name is Kim Yoon-sung and I study Computer Science."가 출력됨
student.getIntroduction();

// 배열 선언: let을 사용하여 재할당 가능한 배열 변수 numbers를 선언
// 배열은 숫자 1, 2, 3, 4, 5를 요소로 포함
let numbers = [1, 2, 3, 4, 5];

// 배열 메소드 사용: push 메소드를 호출하여 배열 끝에 6을 추가
numbers.push(6);

// 콘솔 출력: numbers 배열을 출력
// push로 6이 추가되었으므로 [1, 2, 3, 4, 5, 6]이 출력됨
console.log(numbers);

 

  • 세번째 코드
// setTimeout: 지정된 시간(밀리초) 후에 함수를 한 번 실행하는 내장 함수
// 3000ms(3초) 후에 익명 함수를 실행
setTimeout(function() {
    // 3초 후에 콘솔에 메시지 출력
    console.log("3초가 지났어요!");
}, 3000);

// 변수 선언: 카운트를 저장할 변수 count를 0으로 초기화
let count = 0;

// setInterval: 지정된 시간 간격(밀리초)마다 함수를 반복 실행하는 내장 함수
// 1000ms(1초)마다 익명 함수를 실행, intervalId에 interval의 ID를 저장
let intervalId = setInterval(function() {
    // count를 1씩 증가
    count++;
    // 현재 count 값을 포함한 메시지 출력
    console.log(count + "초마다 메시지가 출력됩니다.");
    // count가 5 이상이면 반복 중지
    if (count >= 5) {
        // clearInterval: intervalId를 사용해 setInterval 반복을 중지
        clearInterval(intervalId);
    }
}, 1000);

// 배열 선언: fruits 배열에 문자열 요소 "apple", "banana", "cherry"를 포함
let fruits = ["apple", "banana", "cherry"];

// forEach: 배열의 각 요소에 대해 주어진 함수를 실행하는 배열 메소드
// 각 요소(fruit)를 콘솔에 출력
fruits.forEach(function(fruit) {
    console.log(fruit);
    // 출력:
    // apple
    // banana
    // cherry
});

// 배열 선언: numbers 배열에 숫자 요소 1, 2, 3, 4, 5를 포함
let numbers = [1, 2, 3, 4, 5];

// map: 배열의 각 요소에 대해 주어진 함수를 적용한 결과를 새로운 배열로 반환
// 각 요소(number)를 2배로 만들어 새로운 배열 doubledNumbers에 저장
let doubledNumbers = numbers.map(function(number) {
    return number * 2; // 각 숫자를 2배로 변환
});
// doubledNumbers 배열 출력: [2, 4, 6, 8, 10]
console.log(doubledNumbers);

// filter: 배열의 각 요소에 대해 주어진 조건을 만족하는 요소만 새로운 배열로 반환
// 짝수인 요소만 필터링하여 새로운 배열 evenNumbers에 저장
let evenNumbers = numbers.filter(function(number) {
    return number % 2 === 0; // number가 짝수일 경우 true 반환
});
// evenNumbers 배열 출력: [2, 4]
console.log(evenNumbers);

 


다음과 같은 내용에 도전해봅시다.

var, let과 const의 차이점 이해하기

  var let const
스코프 함수 스코프 블록 스코프 블록 스코프
재선언 가능 불가 불가
재할당 가능 가능 불가
호이스팅 undefined TDZ TDZ
권장 여부 비권장 권장 권장(상수용

 

 

Arrow Function 이해하기
- Arrow Function(화살표 함수)은 ES6에서 도입된 간결한 함수 표현식입니다. function 키워드 대신 =>를 사용하며, this 바인딩 방식이 다릅니다.

장점 주의점
간결한 문법 this가 고정되므로 객체 메소드나 이벤트 핸들러에서 부적합할 수 있음
this가 렉시컬 스코프로 고정되어 예측 가능 arguments 객체 사용 불가
배열 메소드(mpa, forEach 등)와 함께 사용 시 유용  

 

-  예시

const obj = {
    name: "홍길동",
    sayHello: function() {
        setTimeout(function() {
            console.log(this.name); // 일반 함수: this는 setTimeout의 this (undefined)
        }, 1000);

        setTimeout(() => {
            console.log(this.name); // Arrow Function: this는 obj (김철수)
        }, 1000);
    }
};
obj.sayHello();
// 출력:
// undefined
// 홍길동

 

 

룰렛 게임 완성하기

주어진 코드

<!DOCTYPE html>
<html>
  <head>
    <title>Roulette Game</title>
    <script></script>
  </head>
  <body>
    <div id="roulette">1</div>
    <button id="stopButton">정지</button>

    <script>
      const values = [1, 2, 3, 4, 5, 6];

      const rouletteDisplay = document.getElementById("roulette");

      let intervalId = null;

      let currentIndex = 0;

      function startRoulette() {
        intervalId = //interval 설정하기
      }

      document.getElementById("stopButton").addEventListener("click", () => {
        clearInterval(intervalId);

        alert("선택된 숫자: " + values[currentIndex]);
      });

      startRoulette();
    </script>
  </body>
</html>

 

완성된 코드

<!DOCTYPE html>
<html>
  <head>
    <title>Roulette Game</title>
  <style>
      #roulette { /* 룰렛 숫자가 표시될 div 요소의 스타일 */
        font-size: 48px; /* 글자 크기를 48px로 설정 */
        text-align: center; /* 텍스트를 가운데 정렬 */
        margin: 20px; /* 외부 여백을 20px로 설정 */
        padding: 20px; /* 내부 여백을 20px로 설정 */
        border: 2px solid #333; /* 2px 두께의 검은색(#333) 테두리 */
        width: 100px; /* div의 너비를 100px로 설정 */
        margin: 20px auto; /* 상하 여백 20px, 좌우 여백 auto로 가운데 정렬 */
      }
      #stopButton { /* 정지 버튼의 스타일 */
        display: block; /* 버튼을 블록 요소로 설정하여 한 줄 차지 */
        margin: 0 auto; /* 좌우 여백 auto로 가운데 정렬 */
        padding: 10px 20px; /* 상하 10px, 좌우 20px 내부 여백 */
        font-size: 16px; /* 글자 크기를 16px로 설정 */
        border: none; /* 테두리 제거 */
        cursor: pointer; /* 마우스 커서를 포인터로 변경 (클릭 가능 표시) */
      }
    </style>
  </head>
  <body>
    <!-- 룰렛 숫자가 표시될 div -->
    <div id="roulette">1</div>
    <!-- 룰렛을 멈추는 버튼 -->
    <button id="stopButton">정지</button>

    <script>
      // 룰렛에 표시될 숫자 배열 (const로 선언: 재할당 불가)
      const values = [1, 2, 3, 4, 5, 6];

      // DOM 요소: 룰렛 숫자가 표시될 div 요소 가져오기
      const rouletteDisplay = document.getElementById("roulette");

      // intervalId 변수: setInterval의 ID를 저장 (let으로 선언: 재할당 필요)
      let intervalId = null;

      // currentIndex 변수: 현재 표시 중인 숫자의 인덱스 (let으로 선언: 재할당 필요)
      let currentIndex = 0;

      // startRoulette 함수: 룰렛을 시작하는 함수
      function startRoulette() {
        // setInterval: 100ms마다 숫자를 변경하는 타이머 설정
        intervalId = setInterval(function() { // Arrow Function 대신 일반 function 사용
          // 현재 인덱스를 1 증가
          currentIndex = currentIndex + 1;

          // 만약 currentIndex가 배열의 길이(6) 이상이 되면 0으로 초기화 (순환)
          if (currentIndex >= values.length) { // % 연산 대신 if 조건문으로 변경
            currentIndex = 0; // 배열의 처음으로 돌아감
          }

          // 룰렛 div에 현재 숫자 표시
          rouletteDisplay.textContent = values[currentIndex]; // 현재 인덱스의 숫자를 div에 표시
        }, 100); // 100ms 간격으로 실행 (0.1초마다 숫자 변경)
      }

      // stopButton 클릭 이벤트 리스너: 버튼 클릭 시 룰렛 멈춤
      document.getElementById("stopButton").addEventListener("click", () => {
        // clearInterval: intervalId를 사용해 setInterval 중지
        clearInterval(intervalId);
        // 현재 선택된 숫자를 알림창으로 표시
        alert("선택된 숫자: " + values[currentIndex]);
      });

      // 페이지 로드 시 룰렛 자동 시작
      startRoulette();
    </script>
  </body>
</html>