🔥딱 8일간! 인프런x토스x허먼밀러 역대급 혜택

[인프런 워밍업 스터디 클럽 3기 FE] 1주차 발자국

[인프런 워밍업 스터디 클럽 3기 FE] 1주차 발자국

강의 내용을 정리하기엔 많아서 강의 내용 보다는 들으면서 궁금했던 내용 위주로 정리해보았다.


강의를 들으며 궁금했던 점 🧐

1. Symbol 타입

강사님이 생략하고 넘어간 타입인데 궁금해서 찾아봤다.

Symbol이란?

ES6에서 추가된 Primitive 타입 중 하나. 고유하고 변경 불가능한(immutable) 값을 생성할 때 사용된다. 또한 값을 은닉하고 싶을 때 사용한다.

const sym1 = Symbol();
const sym2 = Symbol();

console.log(sym1 === sym2); // false
  • Symbol() 함수를 호출해 생성함

  • 위 코드는 각각 새로운 Symbol을 생성하므로 다른 값을 가짐

const sym3 = Symbol("description");
const sym4 = Symbol("description");

console.log(sym3 === sym4); // false
  • 생성 시 description을 받을 수 있지만 심볼의 고유성과는 연관이 없다.

  • 디버깅 용도로 사용된다.

Symbol의 특징

  1. 고유성

     

    1. 위와 같이 같은 설명을 가진 Symbol도 항상 다른 값이다.

  2. 변경 불가능

    1. 한번 생성된 Symbol은 변경 불가능하다.

  3. 자동 형 변환 불가

    1. 자동으로 문자열이나 숫자로 변환되지 않는다.

Symbol과 객체 프로퍼티

  • 객체의 고유한 프로퍼티 키로 사용이 가능하다

const ID = Symbol("id");

const user = {
  name: "Alice",
  [ID]: 12345  // Symbol을 키로 사용
};

console.log(user[ID]); // 12345
console.log(Object.keys(user)); // ["name"]
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]
  • Object.keys(), Object.values() 등으로 확인할 수 없음 (은닉)

  • Object.getOwnPropertySymbols()로만 가져올 수 있다.

Symbol.for과 전역 심볼 레지스트리

  • Symbol.for()를 사용하면 Global Symbol Registry에 심볼을 저장하고 검색할 수 있다.

  • Symbol.for("key")를 사용하면 같은 key를 가진 Symbol을 공유할 수 있다.

const sym1 = Symbol.for("sharedKey");
const sym2 = Symbol.for("sharedKey");

console.log(sym1 === sym2); // true (같은 키를 공유하므로 동일한 심볼)
  • Symbol.keyFor()로 전역 레지스트리에 저장된 Symbol의 key를 찾을 수 있다.

const globalSym = Symbol.for("globalSymbol");

console.log(Symbol.keyFor(globalSym)); // "globalSymbol"

const localSym = Symbol("localSymbol");
console.log(Symbol.keyFor(localSym)); // undefined (전역 레지스트리에 등록되지 않음)

전역 심볼 레지스트리, 그래서 이거 언제 쓸까?

  • 모듈간 데이터 공유 시, 같은 키를 사용하면 다른 모듈에서도 동일한 Symbol을 재사용 할 수 있다.

  • 싱글톤 패턴 구현 시 사용한다.

    • 애플리케이션 내에서 유일한 인스턴스 유지할 때 활용한다.

내장 심볼 (Well-known Symbols)

  • ECMAScript에서는 특정 기능을 커스터마이징 할 수 있도록 미리 정의된 Symbol을 제공한다.

  • Javascript 엔진의 기본 동작을 변경할 수 있도록 설계된 심볼이다.

  • 활용 시 객체의 이터러블 구현, 원시값 반환, instanceof 동작 변경 등을 할 수 있다.

 

  • 내장 심볼 예시

imageKotlin의 override와 비슷한 듯 하다.

 

2. for 문에서 출력하는 부분

const user = {
    name: "Ko",
    province: "경기도",
    city: "용인시",
};

for (let x in user) {
    console.log(`${x}: ${user[x]}`);
}

x 그리고 user[x]로 출력하고 있다.

user로 출력하면 어떻게 나오는지 확인해보니 [object Object]라고 나온다.

찾아보니 for .. in .. 으로 반복 시 객체의 key 값으로 꺼내온다고 한다.

따라서 값을 얻기 위해서는 user[x] 와 같은 방식을 사용해야한다.

 

for .. of ..

for .. of .. 의 경우 iterable 객체(Array, Set, Map)의 value 값을 순회한다.

const arr = ["A", "B", "C"];

for (let item of arr) {
    console.log(item); // A, B, C
}

Array의 경우 정상 출력된다.

for (let value of user) { 
    console.log(value);
}
// TypeError: user is not iterable

객체에 사용하면 에러가 난다.

 

객체에서 for .. of .. 를 사용하려면?

Object.keys(), Object.values(), Object.entries() 를 사용하면 가능

 

// key만 순회
for (let key of Object.keys(user)) {
    console.log(key);
}

// value만 순회
for (let value of Object.values(user)) {
    console.log(value);
}

// key-value 쌍 순회
for (let [key, value] of Object.entries(user)) {
    console.log(`${key}: ${value}`);
}

 

3. forEach의 인자

const locations = ['서울', '부산', '경기도', '대구'];
locations.forEach(function (location, index, array) {
    console.log(`${index} : ${location}`);
    console.log(array);
})

여기서 어떻게 location, index, array를 지정해서 쓸 수 있나 궁금해서 찾아봤다.

  • Array.prototype.forEach()는 배열의 각 요소에 대해 한 번씩 지정된 콜백 함수를 실행하는 메서드이다.

  • 기본적으로 매개변수 element, index, array로 내부 콜백함수로 정의되어 있다.

    • element : 현재 순회 중인 배열 요소 (ex: 서울, 부산..)

    • index: 현재 요소의 인덱스 (ex: 0, 1..)

    • forEach()가 호출된 원본 배열 (ex: locations)

       

 

4. Function과 Method의 차이

Function (함수)

  • 독립적으로 정의되고 실행될 수 있는 일반적인 코드 블록

  • 특정 객체에 속하지 않는다

    • function 키워드 또는 const 함수명 = () => {} 형식으로 사용 가능

function sayHello() {
    console.log("Hello, world!");
}

const add = (a, b) => a + b;

 Method (메서드)

  • 객체의 프로퍼티로 정의된 함수

  • 객체에 속하기 때문에 해당 객체의 데이터를 조작하는 역할을 한다.

const person = {
    name: "Alice",
    greet: function() {
        console.log(`Hello, my name is ${this.name}`);
    }
};

This 키워드 차이

  • Method는 객체 내부에서 this를 사용해 객체 프로퍼티에 접근이 가능함

  • Function의 경우 this가 글로벌 객체(ex: window 또는 undefined(strict mode))를 가리킨다.

5. Constructor Function

function Audio(title) {
    this.title = title;
    console.log(this);
}
const audio = new Audio('a')

강의를 보다가, 해당 코드가 왜 title을 따로 설정(?)도 안하고 바로 this.title을 쓸 수 있는지 이해가 안갔다.

관련해서 찾아보니 아래와 같았다.

생성자 함수를 사용해 새로운 객체를 만들고, 그 객체의 속성(title)을 동적으로 할당하는 것

 

new Audio('a')를 실행시 new 키워드의 동작

  1. 새로운 빈 객체 {}를 생성

  2. this를 새로 생성된 객체로 바인딩

  3. 생성자 함수의 코드를 실행 (this.title = title;)

  4. 새로 생성된 객체를 반환

new 없이 호출한다면 this가 전역 객체를 가리키게 되서 title이 전역 변수로 선언되거나 strict mode에서는 오류가 발생한다. 따라서 의도하지 않은 동작을 방지하려면 new를 꼭 사용해야한다.

 

6. Strict Mode

  • "use strict"; 추가시 사용이 가능함

  • var, let, const 없이 변수 선언이 금지된다.

  • 읽기 전용 속성 변경이 금지된다. (애초에 왜 되는데?)

  • 같은 이름의 매개변수가 금지 된다.

  • delete로 변수 삭제가 금지된다.

  • this 값이 undefined가 된다.

    • 일반적으로 JS에서는 this는 전역 객체를 가리키는데 strict mode에서는 this가 undefined로 설정된다.

    • 따라서 잘못된 this 사용을 막는다.

  • 모든 최신 JavaScript 프로젝트에서 기본적으로 사용하는게 좋다.

7. IIFE (Immediately Invoked Function Expression)

기본 구조  

(function() {
    console.log("즉시 실행됨!");
})(); 
(() => {
    console.log("즉시 실행됨!");
})();

왜 IIFE를 사용할까?

  • 전역 변수 오염 방지

JS는 기본적으로 전역 스코프(Global Scope)를 가져서 코드가 길어질 수록 충돌할 가능성이 큼

IIFE를 사용하면 함수 내부의 변수는 외부에서 접근할 수 없도록 보호할 수 있음

 

// 전역 변수를 오염시키는 경우
let counter = 0;

function increment() {
    counter++;
    console.log(counter);
}

increment(); // 1
increment(); // 2
console.log(counter); // 2 (외부에서 counter 변경 가능)

// IIFE를 사용하여 보호
const increment = (() => {
    let counter = 0; // 외부에서 접근 불가
    return () => {
        counter++;
        console.log(counter);
    };
})();

increment(); // 1
increment(); // 2
console.log(typeof counter); // undefined (외부에서 접근 불가)

 

  • 한 번만 실행되는 초기화 코드

즉시 실행 되므로 초기화 코드 실행하는데 유용함

 

  • 클로저 활용 가능

const increment = (() => {
    let counter = 0; // 클로저로 보호된 변수

    return () => {
        counter++;
        console.log(counter);
    };
})();

increment(); // 1
increment(); // 2
increment(); // 3
console.log(typeof counter); // undefined (외부에서 접근 불가)

counter가 클로저에 의해 보호되어 있기 때문에 외부에서 직접 변경이 불가능하고 오직 함수 호출을 통해서만 조작이 가능함

 

객체를 사용해 상태를 관리하는 방식과의 차이

  • IIEF 사용하는 경우

const increment = (() => {
    let counter = 0;
    return () => {
        counter++;
        console.log(counter);
    };
})();

increment(); // 1
increment(); // 2
console.log(typeof counter); // undefined (외부 접근 불가)

IIFE 내부에 counter가 있어서 외부에서 변경이 불가능

  • 객체를 사용하는 경우

const obj = {
    counter: 0,
    increment: function() {
        this.counter++;
        console.log(this.counter);
    }
};

obj.increment(); // 1
obj.increment(); // 2
console.log(obj.counter); // 2 (외부에서 변경 가능)

counter 변수가 객체 내부에 있지만 외부에서 수정 가능

 

  • 차이점 정리

    • IIFE 방식 / 객체 방식

    • 스코프 보호 : counter 외부에서 접근 불가 / counter 외부에서 수정 가능

    • 전역 오염 방지 : 변수는 IIFE 내부에만 존재 / 변수는 객체에 저장되어 외부에서 접근 가능

    • 사용 목적 : 한정된 기능 (ex: 특정 함수의 상태 유지) / 여러 속성과 메서드를 관리하는 구조

       

 

8. Curry Function

커링 함수란?

function addCurry(a) {
    return function (b) {
        return a + b;
    };
}

const add3 = addCurry(3);
console.log(add3(5)); // 8
console.log(addCurry(3)(5)); // 8

함수를 쪼개서 여러 단계로 호출할 수 있도록 변환하는 기법

하나의 함수에서 여러 개의 인자를 한 번에 받는 대신, 부분적으로 받아서 실행하는 방식이다.

강의를 들으면서, 어떻게 쓰는지는 알겠는데 왜 쓰는지 모르겠어서 찾아보았다. 🤔

 

커링 함수, 왜 쓸까?

  • 재사용성 증가

     

function multiply(a) {
    return function (b) {
        return a * b;
    };
}

const double = multiply(2); // 2를 미리 적용
const triple = multiply(3); // 3을 미리 적용

console.log(double(5)); // 10
console.log(triple(5)); // 15

여기서 double은 2를 미리 적용해서 항상 2 * b를 실행하는 함수가 된다.

  • 함수 조합(Function Composition)에 유용함

const add = a => b => a + b;
const multiply = a => b => a * b;

const add5 = add(5);
const multiply3 = multiply(3);

console.log(add5(10)); // 15
console.log(multiply3(10)); // 30

함수를 조합해 더 작은 단위의 함수를 쉽게 만들 수 있다.

부분 적용, 함수 조합, 설정 저장 등에 유용하다.

  • 코드 가독성 및 유지보수성 향상

const log = level => message => console.log(`[${level}] ${message}`);

const errorLog = log("ERROR");
const warningLog = log("WARNING");

errorLog("서버가 다운되었습니다."); // [ERROR] 서버가 다운되었습니다.
warningLog("메모리가 부족합니다."); // [WARNING] 메모리가 부족합니다.

코드를 더 읽기 쉽게 만들고 유지보수하기 쉬운 형태로 관리할 수 있다.

 

9. 생성자 함수와 Class, Object.create()의 차이

생성자 함수

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};

const john = new Person("John", 30);
john.greet(); // "Hello, my name is John and I am 30 years old."
  • 생성자 함수는 new 키워드와 함께 호출될 때 새로운 객체를 생성함.

  • this를 사용해 객체의 속성을 설정한다

  • ES6에 도입된 class 문법이 좀 더 직관적임.

  • 명시적으로 프로토타입을 지정해야 메서드 공유가 가능

  • 복잡해질수록 코드가 길어지고 가독성이 낮아진다.

Class

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

const john = new Person("John", 30);
john.greet(); // "Hello, my name is John and I am 30 years old."
  • class 키워드를 사용해 정의

  • 메서드를 클래스 내부에 선언해 프로토 타입을 활용함

  • 자동으로 프로토타입을 활용해 메서드를 공유함

  • 객체지향 언어와 유사한 형대로 직관적인 코드 작성이 가능하다.

  • 기능적으로 생성자 함수와 동일함

Object.create()

const personPrototype = {
  greet: function() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
};

const john = Object.create(personPrototype);
john.name = "John";
john.age = 30;

john.greet(); // "Hello, my name is John and I am 30 years old." 
  • 특정 객체를 프로토타입으로 명확하게 설정 가능

  • 같은 프로토타입을 공유하면서 새로운 객체 생성 가능

  • 클래스 없이도 객체 상속이 가능

  • 프로토타입을 명확히 컨트롤할 필요가 있을 때 사용하면 유용함

  • 하지만 class가 더 직관적이고 가독성이 좋아서 class를 사용하는 것이 좋음

 


미션 1

  • 메뉴판 화면에서 메뉴 카테고리 선택 시 카테고리별로 보여주는 화면을 만드는 것

해결 과정

해결 방안 고민

  1. HTML을 여러개 사용해 메뉴 클릭시 화면을 전환하게 한다.

  2. 기본 틀 HTML을 하나를 두고 카테고리 클릭 시 JS로 화면을 변경한다

여기서 1번은 비효율적으로 보이고, 또 미션에서 보이는 화면이 각 메뉴별로 동일한 화면을 쓰고 있어서 2번을 채택함.

개발 과정

  • 일단 기본적으로 div로 html/css 뼈대를 잡음

<body>
    <h1 class="food-menu text-3xl font-bold underline">
        Food Menu
    </h1>
    <div class="button-container">
        <button class="food-button">All</button>
        <button class="food-button">Breakfast</button>
        <button class="food-button">Lunch</button>
        <button class="food-button">Shakes</button>
        <button class="food-button">Dinner</button>
    </div>
    <div class="image-container">
        <div class="column">
            <img class="food-img" src="images/food1.jpg"/>
            <img class="food-img" src="images/food2.jpg"/>
            <img class="food-img" src="images/food3.jpg"/>
        </div>
        <div class="column">
            <img class="food-img" src="images/bread.jpg"/>
            <img class="food-img" src="images/breakfast.jpg"/>
            <img class="food-img" src="images/cake.jpg"/>
        </div>
        <div class="column">
            <img class="food-img" src="images/potato.jpg"/>
            <img class="food-img" src="images/shake.jpg"/>
            <img class="food-img" src="images/salad.jpg"/>
        </div>
    </div>
    <script src="js/script.js"></script>
</body>
  • 카테고리 값을 구분하려면 어떻게 해야할까?

    • 사용자 정의 속성을 넣을 수 있음

<body>
    <h1 class="food-menu text-3xl font-bold underline">
        Food Menu
    </h1>
    <div class="button-container">
        <button class="food-button" data-category="all">All</button>
        <button class="food-button" data-category="breakfast">Breakfast</button>
        <button class="food-button" data-category="lunch">Lunch</button>
        <button class="food-button" data-category="shakes">Shakes</button>
        <button class="food-button" data-category="dinner">Dinner</button>
    </div>
    <div class="menu-items">
        <div class="menu-item" data-category="breakfast">
            <img class="food-img" src="images/food1.jpg"/>
        </div>
        <div class="menu-item" data-category="lunch">
            <img class="food-img" src="images/food2.jpg"/>
        </div>
        <div class="menu-item" data-category="shakes">
            <img class="food-img" src="images/food3.jpg"/>
        </div>
        <div class="menu-item" data-category="dinner">
            <img class="food-img" src="images/bread.jpg"/>
        </div>
        <div class="menu-item" data-category="breakfast">
            <img class="food-img" src="images/breakfast.jpg"/>
        </div>
        <div class="menu-item" data-category="lunch">
            <img class="food-img" src="images/cake.jpg"/>
        </div>
        <div class="menu-item" data-category="shakes">
            <img class="food-img" src="images/potato.jpg"/>
        </div>
        <div class="menu-item" data-category="dinner">
            <img class="food-img" src="images/shake.jpg"/>
        </div>
        <div class="menu-item" data-category="breakfast">
            <img class="food-img" src="images/salad.jpg"/>
        </div>
    </div>
    <script src="js/script.js"></script>
</body>

사용자 정의 속성으로 속성을 구분하도록 수정

const buttons = document.querySelectorAll(".food-button");
buttons.forEach(button => {
    button.addEventListener("click", function() {
        const category = this.dataset.category;
        if (category === "all") {
            console.log("all");
        } else {
            console.log("else");
        }
    });
});
  • 사용자 정의 속성 사용시 dataset으로 사용이 가능하다

  • data- 이후의 이름으로 가져올 수 있다.

const buttons = document.querySelectorAll(".food-button");
const menuItems = document.querySelectorAll(".menu-item");
buttons.forEach(button => {
    button.addEventListener("click", function() {
        const category = this.dataset.category;
        menuItems.forEach(item => {
            if (category === "all") {
                item.style.display = "flex";
            } else {
                if (item.dataset.category === category) {
                    item.style.display = "flex";
                } else {
                    item.style.display = "none";
                }
            }
        });
    });
});
  • display=none 옵션으로 일부 메뉴를 가려준다.

const buttons = document.querySelectorAll(".food-button");
const menuItems = document.querySelectorAll(".menu-item");
buttons.forEach(button => {
    button.addEventListener("click", function () {
        const buttonCategory = this.dataset.category;
        menuItems.forEach(item => {
            const itemCategory = item.dataset.category;
            item.style.display = ["all", itemCategory].includes(buttonCategory) ? "flex" : "none"
        });
    });
});
  • 코드를 좀 더 함수형으로 리팩토링 해본다.

 회고

  • CSS가 더 어렵다 😔

  • 아직 JS에 익숙하지 않아서 머리로 로직을 세우는 것과 실제로 어떤걸 써야할지 감이 잘 안오는 것 같다.

  • 실제로 구현해보니 강의만 듣는 것보다 어떤식으로 개발해야할지 감이 오는 것 같다.

 

미션2

  • 가위바위보

  • 플레이어와 컴퓨터가 있고 가위/바위/보 선택시 승리한 경우 플레이어 또는 컴퓨터의 카운트가 올라감

  • 최종 게임 승리 여부를 출력해 줌.

해결 과정

기능 파악

  1. 플레이어와 컴퓨터는 각각의 승리 카운트 상태값을 가지며 승패 여부에 따라 카운트가 업데이트 됨.

  2. 가위/바위/보 클릭 시, 컴퓨터는 랜덤하게 가위/바위/보 값을 가짐

  3. 가위/바위/보 클릭 시, 선택하기 카운트가 줄어듬.

  4. 플레이어의 가위/바위/보 상태와 컴퓨터의 상태를 비교해 승리 여부를 판단함

  5. 최종 플레이어 승리 카운트와 컴퓨터의 승리 카운트를 비교해 최종 승패 여부를 출력함.

  6. 다시 시작 클릭시 게임을 다시 시작할 수 있음.

개발 과정

  • html과 연동 하는 부분이 익숙하지 않아서, 일단 JS로 로직부터 개발함

let playerWinCount = 0;
let computerWinCount = 0;
let roundCount = 10;

function computerPlay() {
    let choices = ['rock', 'paper', 'scissors'];
    return choices[Math.floor(Math.random() * choices.length)];
};

function playRound(playerSelection, computerSelection) {
    if (playerSelection === computerSelection) {
        return '무승부';
    } else if (
        (playerSelection === 'rock' && computerSelection === 'scissors') ||
        (playerSelection === 'paper' && computerSelection === 'rock') ||
        (playerSelection === 'scissors' && computerSelection === 'paper')
    ) {
        playerWinCount++;
        return '플레이어 승리';
    } else {
        computerWinCount++;
        return '컴퓨터 승리';
    }
}

function playerPlay() {
    document.querySelectorAll('.player-choice').forEach((button) => {
        button.addEventListener('click', function() {
            return button.id;
        });
    });
}

function game() {
    for (let i = 0; i < roundCount; i++) {
        let playerSelection = playerPlay();
        let computerSelection = computerPlay();
        console.log(playRound(playerSelection, computerSelection));
    }
    if (playerWinCount > computerWinCount) {
        console.log('플레이어 승리');
    } else if (playerWinCount < computerWinCount) {
        console.log('컴퓨터 승리');
    } else {
        console.log('무승부');
    }
}
  • 이후 html 연동하는 부분을 붙임 (길어서 코드 생략)

  • 최종 코드

let playerWinCount = 0;
let computerWinCount = 0;
let roundCount = 10;

const resultDiv = document.querySelector('#result');
const resultMessage = document.querySelector('#result-message');
const playerWinCountDiv = document.querySelector('#player-win-count');
const computerWinCountDiv = document.querySelector('#computer-win-count');
const roundCountDisplay = document.querySelector('#round-count');
const resetButton = document.querySelector('#reset');

function computerPlay() {
    let choices = ['rock', 'paper', 'scissors'];
    return choices[Math.floor(Math.random() * choices.length)];
};

function playRound(playerSelection, computerSelection) {
    if (playerSelection === computerSelection) {
        return '무승부';
    } else if (
        (playerSelection === 'rock' && computerSelection === 'scissors') ||
        (playerSelection === 'paper' && computerSelection === 'rock') ||
        (playerSelection === 'scissors' && computerSelection === 'paper')
    ) {
        playerWinCount++;
        updateUI();
        return '플레이어 승리';
    } else {
        computerWinCount++;
        updateUI();
        return '컴퓨터 승리';
    }
}

function updateUI() {
    playerWinCountDiv.textContent = playerWinCount;
    computerWinCountDiv.textContent = computerWinCount;
    roundCountDisplay.textContent = roundCount;
}

function game(playerSelection) {
    if (roundCount === 0) {
        alert('게임이 끝났습니다. 게임을 다시 시작하려면 [다시 시작] 버튼을 누르세요.');
        return;
    }
    roundCount--;
    updateUI();

    let computerSelection = computerPlay();
    let playResult = playRound(playerSelection, computerSelection);
    console.log(playResult);
    resultDiv.style.display = 'block';
    resultMessage.textContent = playResult;

    if (roundCount === 0) endGame();
}

function endGame() {
    if (playerWinCount > computerWinCount) {
        console.log('게임에서 이겼습니다.');
        resultMessage.textContent = '게임에서 이겼습니다.';
    } 
    if (playerWinCount < computerWinCount) {
        console.log('게임에서 졌습니다.');
        resultMessage.textContent = '게임에서 졌습니다.';
    } 
    if (playerWinCount === computerWinCount) {
        console.log('무승부입니다.');
        resultMessage.textContent = '무승부입니다.';
    }
    resultDiv.style.display = 'none';
    resetButton.style.display = 'block';
}

function resetGame() {
    playerWinCount = 0;
    computerWinCount = 0;
    roundCount = 10;

    updateUI();
    resultMessage.textContent = '';
    resetButton.style.display = 'none';
}

document.querySelectorAll('.player-choice').forEach((button) => {
    button.addEventListener('click', function() {
        game(button.id);
    });
});

resetButton.addEventListener('click', function() {
    resetGame()
});

회고

  • CSS는 신경 안쓰고 기능 개발에만 집중하니 훨씬 수월했다.

  • 처음에는 막막했는데 일단 BE 개발하듯 로직부터 접근하니 개발하기 쉬웠다.

 

미션3

  • 퀴즈 앱

해결 과정

기능 파악

  • 정답을 틀리거나 맞출 경우 Next 버튼이 생성되며 다음 퀴즈를 할 수 있음

  • 퀴즈는 회색 화면에 answer 3가지가 존재 (값/정답이 없는 경우)

  • 정답을 맞출 경우 초록 화면 / 틀릴 경우 빨간 화면

  • 퀴즈 종료 시 restart 버튼이 생성 됨.

개발 과정

  • 로직개발까지는 문제 없었으나 next 버튼과 reset이 제대로 동작안하는 이슈가 발견됨

  • 알고보니 addEventListener 를 함수 내부에 선언해 함수를 중복해서 사용해 리스너가 중복으로 실행된 것

  • 리스너를 함수 밖으로 빼고 해결함.

회고

  • Listener의 동작 방식을 모르고 사용한 것 같다.

  • 글로벌 변수를 안쓰는 방향으로 가고 싶은데 어떤식으로 해야할지 아직은 잘 모르겠다.

 

미션 4

  • 책 관리 앱

     

    해결 과정

    기능 파악

  • form에 책 데이터를 입력 받는다

  • 데이터 입력 후 submit 시 하단에 책목록에 추가된다

  • 책목록에 있는 데이터는 X 버튼으로 삭제할 수 있다.

  • 책 추가 또는 삭제시 알림 메세지가 뜬다.

    개발 과정

  • submit 이벤트를 어떻게 받는지부터 고민 했는데 addEventListener에서 가능했다

  • 받아온 event는 event.target 을 통해 값을 꺼내올 수 있다.

  • createElement를 통해 책목록, 알림요소들을 추가해주었다.

    회고 

  •  이전 과제들보다 오히려 쉬운 느낌이었다. (특히 퀴즈..)

 

미션 5

  • Github Finder 앱

해결 과정

기능 파악

  • 유저가 입력하는대로 Github User를 검색하는 App

  • Search 버튼을 따로 누르지 않고 입력 받는대로 API 호출을 한다.

개발 과정

  • 일단 API 호출을 하기 위해 await fetch 를 사용함

  • 그리고 UI를 만듦...

  • API를 여러번 호출하는데 debounce 라는 개념이 있어서 알아보았다

     

회고

  • debounce 를 적용하는 것 외에는 딱히 어려운 점은 없었다.

댓글을 작성해보세요.

채널톡 아이콘