Category: JavaScript

  • JS의 비동기

    🔍 1. 동기 vs 비동기가 뭔가요?

    동기 처리 (Synchronous):

    console.log("1번");
    console.log("2번");
    console.log("3번");
    
    // 결과: 1번 → 2번 → 3번 (순서대로)
    // 앞의 작업이 끝나야 다음 작업 실행
    

    비동기 처리 (Asynchronous):

    console.log("1번");
    
    setTimeout(() => {
      console.log("2번 (3초 후)");
    }, 3000);
    
    console.log("3번");
    
    // 결과: 1번 → 3번 → (3초 후) 2번
    // 2번을 기다리지 않고 3번 먼저 실행!
    

    ⏰ 2. 왜 비동기가 필요할까?

    // 만약 동기적으로만 처리한다면...
    console.log("서버에 데이터 요청 시작");
    // 여기서 3초 동안 멈춤... (서버 응답 기다림)
    // 사용자는 3초 동안 아무것도 할 수 없음!
    console.log("데이터 받음");
    console.log("화면에 버튼 클릭 처리");
    
    // 비동기로 처리하면...
    console.log("서버에 데이터 요청 시작");
    console.log("화면에 버튼 클릭 처리"); // 바로 실행!
    // 서버 응답이 오면 그때 처리
    

    🔄 3. 비동기 처리의 3가지 방법

    방법 1: 콜백 (Callback) – 구식

    function getData(callback) {
      setTimeout(() => {
        const data = "서버에서 받은 데이터";
        callback(data);  // 작업 완료 후 콜백 호출
      }, 2000);
    }
    
    getData((result) => {
      console.log(result);  // "서버에서 받은 데이터"
    });
    
    console.log("이건 먼저 실행됨");
    

    콜백의 문제점 – 콜백 지옥:

    // 단계별 작업이 필요한 경우
    getUserData((user) => {
      getOrderData(user.id, (orders) => {
        getPaymentData(orders[0].id, (payment) => {
          processPayment(payment, (result) => {
            console.log("결제 완료!");
            // 중첩이 너무 깊어짐... 😵
          });
        });
      });
    });
    

    방법 2: Promise – 개선된 방법

    function getData() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          const success = true;
          
          if (success) {
            resolve("서버에서 받은 데이터");  // 성공
          } else {
            reject("에러 발생!");             // 실패
          }
        }, 2000);
      });
    }
    
    // Promise 사용법
    getData()
      .then((result) => {
        console.log(result);  // 성공했을 때
      })
      .catch((error) => {
        console.log(error);   // 실패했을 때
      });
    

    Promise 체이닝:

    getUserData()
      .then((user) => {
        return getOrderData(user.id);
      })
      .then((orders) => {
        return getPaymentData(orders[0].id);
      })
      .then((payment) => {
        return processPayment(payment);
      })
      .then((result) => {
        console.log("결제 완료!");
      })
      .catch((error) => {
        console.log("어디선가 에러:", error);
      });
    

    방법 3: async/await – 최신 방법 (제일 좋음)

    async function processOrder() {
      try {
        const user = await getUserData();
        const orders = await getOrderData(user.id);
        const payment = await getPaymentData(orders[0].id);
        const result = await processPayment(payment);
        
        console.log("결제 완료!");
        // 마치 동기 코드처럼 보이지만 비동기!
      } catch (error) {
        console.log("에러:", error);
      }
    }
    
    processOrder();
    

    🌐 4. fetch API로 실제 사용해보기

    기본 fetch 사용법:

    // Promise 방식
    fetch('https://jsonplaceholder.typicode.com/posts/1')
      .then(response => response.json())
      .then(data => {
        console.log(data);
      })
      .catch(error => {
        console.log('에러:', error);
      });
    
    // async/await 방식 (더 깔끔)
    async function getPost() {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
        const data = await response.json();
        console.log(data);
      } catch (error) {
        console.log('에러:', error);
      }
    }
    
    getPost();
    

    여러 API 동시에 호출:

    async function getMultipleData() {
      try {
        // 동시에 여러 요청 보내기
        const [usersResponse, postsResponse] = await Promise.all([
          fetch('https://jsonplaceholder.typicode.com/users'),
          fetch('https://jsonplaceholder.typicode.com/posts')
        ]);
        
        const users = await usersResponse.json();
        const posts = await postsResponse.json();
        
        console.log('사용자:', users);
        console.log('게시글:', posts);
      } catch (error) {
        console.log('에러:', error);
      }
    }
    

    POST 요청 보내기:

    async function createPost() {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            title: '새 게시글',
            body: '게시글 내용',
            userId: 1
          })
        });
        
        const newPost = await response.json();
        console.log('생성된 게시글:', newPost);
      } catch (error) {
        console.log('에러:', error);
      }
    }
    

    ⚠️ 5. 자주 하는 실수들

    실수 1: await 없이 사용

    // ❌ 잘못된 방법
    async function badExample() {
      const response = fetch('https://api.example.com/data');
      console.log(response); // Promise 객체가 출력됨 (데이터 아님!)
    }
    
    // ✅ 올바른 방법
    async function goodExample() {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      console.log(data); // 실제 데이터 출력
    }
    

    실수 2: 에러 처리 안 함

    // ❌ 위험한 방법
    async function risky() {
      const response = await fetch('https://wrong-url.com/data');
      const data = await response.json(); // 에러 발생 시 앱 크래시!
    }
    
    // ✅ 안전한 방법
    async function safe() {
      try {
        const response = await fetch('https://api.example.com/data');
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const data = await response.json();
        return data;
      } catch (error) {
        console.log('데이터를 가져올 수 없습니다:', error);
        return null;
      }
    }
    

    실수 3: 순차 처리 vs 병렬 처리 혼동

    // ❌ 느린 방법 (순차 처리)
    async function slow() {
      const user = await fetch('/api/user');     // 1초 기다림
      const orders = await fetch('/api/orders'); // 또 1초 기다림
      // 총 2초 소요
    }
    
    // ✅ 빠른 방법 (병렬 처리)
    async function fast() {
      const [userResponse, ordersResponse] = await Promise.all([
        fetch('/api/user'),
        fetch('/api/orders')
      ]);
      // 동시에 요청해서 1초만 소요
    }
    

    🎯 6. 실제 사용 예시

    class WeatherApp {
      constructor() {
        this.apiKey = 'your-api-key';
        this.baseUrl = 'https://api.openweathermap.org/data/2.5';
      }
      
      async getCurrentWeather(city) {
        try {
          this.showLoading();
          
          const response = await fetch(
            `${this.baseUrl}/weather?q=${city}&appid=${this.apiKey}&units=metric`
          );
          
          if (!response.ok) {
            throw new Error(`날씨 정보를 찾을 수 없습니다: ${response.status}`);
          }
          
          const weatherData = await response.json();
          this.displayWeather(weatherData);
          
        } catch (error) {
          this.showError(error.message);
        } finally {
          this.hideLoading();
        }
      }
      
      showLoading() {
        console.log('날씨 정보 로딩 중...');
      }
      
      hideLoading() {
        console.log('로딩 완료');
      }
      
      displayWeather(data) {
        console.log(`${data.name}: ${data.main.temp}°C, ${data.weather[0].description}`);
      }
      
      showError(message) {
        console.log('에러:', message);
      }
    }
    
    // 사용
    const weatherApp = new WeatherApp();
    weatherApp.getCurrentWeather('Seoul');
    

    💡 핵심 정리:

    1. 비동기가 필요한 이유:

    • 네트워크 요청, 파일 읽기 등 시간이 걸리는 작업
    • 사용자 경험 향상 (멈추지 않는 앱)

    2. async/await가 최고:

    • 코드가 읽기 쉬움
    • 에러 처리가 간단 (try/catch)
    • Promise의 장점 + 동기 코드 같은 가독성

    3. 꼭 기억할 것:

    • awaitasync 함수 안에서만 사용
    • 에러 처리는 필수 (try/catch)
    • 병렬 처리가 필요하면 Promise.all() 사용

    4. fetch API 패턴:

    async function apiCall() {
      try {
        const response = await fetch(url);
        if (!response.ok) throw new Error('Network error');
        const data = await response.json();
        return data;
      } catch (error) {
        console.log('Error:', error);
        returnnull;
      }
    }
    
  • JS의 프로토타입, 클래스, 상속

    🔧 1. Prototype의 기초 개념

    1-1. 모든 객체는 prototype을 가진다

    // 기본 객체도 prototype을 가짐
    const obj = { name: "김철수" };
    console.log(obj.__proto__ === Object.prototype); // true
    
    // 배열도 prototype을 가짐
    const arr = [1, 2, 3];
    console.log(arr.__proto__ === Array.prototype); // true
    
    // 함수도 prototype을 가짐
    function hello() {}
    console.log(hello.__proto__ === Function.prototype); // true
    
    /*
    1. Prototype 객체는 하나만 존재
    
    모든 인스턴스가 같은 prototype 객체를 참조
    메소드들도 하나만 존재
    
    2. 하지만 실행 시 this가 다름
    
    3. 그래서 같은 메소드로 다른 결과
    
    메소드는 공유하지만
    각 인스턴스의 데이터(this.name, this.count 등)는 독립적
    */

    1-2. Prototype Chain의 원리

    const person = { name: "김철수" };
    
    // person 객체에서 toString()을 찾는 과정:
    // 1. person 객체 자체에서 찾기 → 없음
    // 2. person.__proto__ (Object.prototype)에서 찾기 → 있음!
    person.toString(); // "[object Object]"
    
    // 이것이 prototype chain
    // person → Object.prototype → null
    

    1-3. 생성자 함수와 prototype

    // 생성자 함수
    function Person(name) {
      this.name = name;
    }
    
    // prototype에 메소드 추가
    Person.prototype.greet = function() {
      return `Hello, I'm ${this.name}`;
    };
    
    Person.prototype.age = 0; // 기본값 설정
    
    // 인스턴스 생성
    const kim = new Person("김철수");
    const lee = new Person("이영희");
    
    // 메소드 사용 (prototype에서 상속받음)
    console.log(kim.greet()); // "Hello, I'm 김철수"
    console.log(lee.greet()); // "Hello, I'm 이영희"
    
    // prototype은 모든 인스턴스가 공유
    console.log(kim.__proto__ === Person.prototype); // true
    console.log(lee.__proto__ === Person.prototype); // true
    console.log(kim.__proto__ === lee.__proto__);    // true
    

    1-4. new 키워드의 마법

    function Person(name) {
      this.name = name;
      this.energy = 100;
    }
    
    // new Person("김철수")가 하는 일:
    // 1. 빈 객체 생성: {}
    // 2. 그 객체의 __proto__를 Person.prototype으로 설정
    // 3. Person 함수를 그 객체 컨텍스트로 실행 (this = 새 객체)
    // 4. 완성된 객체 반환
    
    const kim = new Person("김철수");
    // kim = { name: "김철수", energy: 100, __proto__: Person.prototype }
    

    🎯 2. Class 문법 (ES6)

    2-1. Class의 기본 구조

    class Person {
      // constructor: 인스턴스 생성 시 호출
      constructor(name, age) {
        this.name = name;
        this.age = age;
        this.energy = 100;
      }
      
      // 메소드들 (자동으로 prototype에 추가됨)
      greet() {
        return `Hello, I'm ${this.name}`;
      }
      
      sleep() {
        this.energy = 100;
        return `${this.name} is sleeping`;
      }
      
      // 정적 메소드 (클래스 자체에 속함)
      static getSpecies() {
        return "Homo sapiens";
      }
    }
    
    // 사용법
    const kim = new Person("김철수", 25);
    console.log(kim.greet());        // "Hello, I'm 김철수"
    console.log(Person.getSpecies()); // "Homo sapiens"
    

    2-2. Class vs Function 생성자 비교

    // Function 방식 (ES5)
    function PersonFunc(name) {
      this.name = name;
    }
    PersonFunc.prototype.greet = function() {
      return `Hello, I'm ${this.name}`;
    };
    
    // Class 방식 (ES6) - 내부적으로는 같음
    class PersonClass {
      constructor(name) {
        this.name = name;
      }
      
      greet() {
        return `Hello, I'm ${this.name}`;
      }
    }
    
    // 결과는 동일
    const func = new PersonFunc("김철수");
    const clss = new PersonClass("이영희");
    
    console.log(func.__proto__ === PersonFunc.prototype); // true
    console.log(clss.__proto__ === PersonClass.prototype); // true
    

    2-3. Class의 특별한 기능들

    class Person {
      constructor(name) {
        this.name = name;
        this._age = 0; // private 관례 (실제로는 public)
      }
      
      // Getter
      get age() {
        return this._age;
      }
      
      // Setter
      set age(value) {
        if (value < 0) {
          throw new Error("나이는 음수가 될 수 없습니다");
        }
        this._age = value;
      }
      
      // 정적 메소드
      static compare(person1, person2) {
        return person1.age - person2.age;
      }
    }
    
    const kim = new Person("김철수");
    kim.age = 25;        // setter 호출
    console.log(kim.age); // getter 호출, 25
    
    // 정적 메소드 사용
    const lee = new Person("이영희");
    lee.age = 30;
    console.log(Person.compare(kim, lee)); // -5
    
    /*
    정적 메소드:
    
    클래스 자체에 속함 (인스턴스에 속하지 않음)
    클래스명.메소드명() 으로 호출
    this는 클래스 자체를 가리킴
    인스턴스 데이터에 접근 불가
    유틸리티, 팩토리, 검증 함수 등에 적합
    
    인스턴스 메소드:
    
    각 인스턴스에 속함
    인스턴스.메소드명() 으로 호출
    this는 해당 인스턴스를 가리킴
    인스턴스 데이터에 접근 가능
    
    쉽게 기억하기: "인스턴스 없이도 쓸 수 있는 기능 = 정적 메소드"
    */

    🔗 3. Prototype 방식 상속

    3-1. 단계별 상속 구현

    // 1단계: 부모 생성자
    function Animal(name, habitat) {
      this.name = name;
      this.habitat = habitat;
      this.energy = 100;
    }
    
    Animal.prototype.eat = function(food) {
      this.energy += 20;
      return `${this.name}가 ${food}를 먹었습니다`;
    };
    
    Animal.prototype.sleep = function() {
      this.energy = 100;
      return `${this.name}가 잠을 잡니다`;
    };
    
    // 2단계: 자식 생성자
    function Dog(name, habitat, breed) {
      // 부모 생성자 호출 (중요!)
      Animal.call(this, name, habitat);
      this.breed = breed;
      this.loyalty = 100;
    }
    
    // 3단계: prototype chain 연결 (핵심!)
    Dog.prototype = Object.create(Animal.prototype);
    
    // 4단계: constructor 복구 (중요!)
    Dog.prototype.constructor = Dog;
    
    // 5단계: 자식만의 메소드 추가
    Dog.prototype.bark = function() {
      this.energy -= 10;
      return `${this.name}가 멍멍!`;
    };
    
    Dog.prototype.wagTail = function() {
      this.loyalty += 5;
      return `${this.name}가 꼬리를 흔듭니다`;
    };
    
    // 6단계: 부모 메소드 오버라이드
    Dog.prototype.eat = function(food) {
      // 부모 메소드 호출
      const result = Animal.prototype.eat.call(this, food);
      
      // 자식만의 추가 로직
      if (food === "뼈다귀") {
        this.energy += 10;
        return result + " (뼈다귀 보너스!)";
      }
      return result;
    };
    

    3-2. 복잡한 상속 구조

    // 3세대 상속: Animal → Dog → GermanShepherd
    function GermanShepherd(name, habitat) {
      Dog.call(this, name, habitat, "German Shepherd");
      this.trainedSkills = [];
    }
    
    GermanShepherd.prototype = Object.create(Dog.prototype);
    GermanShepherd.prototype.constructor = GermanShepherd;
    
    GermanShepherd.prototype.guard = function() {
      this.energy -= 15;
      return `${this.name}가 경비를 섭니다`;
    };
    
    GermanShepherd.prototype.learn = function(skill) {
      this.trainedSkills.push(skill);
      return `${this.name}가 ${skill}을 배웠습니다`;
    };
    
    // 사용법
    const rex = new GermanShepherd("렉스", "집");
    console.log(rex.eat("뼈다귀"));   // Animal + Dog 로직
    console.log(rex.bark());         // Dog 로직
    console.log(rex.guard());        // GermanShepherd 로직
    console.log(rex instanceof GermanShepherd); // true
    console.log(rex instanceof Dog);            // true
    console.log(rex instanceof Animal);         // true
    

    🎯 4. Class 방식 상속

    4-1. extends와 super 사용법

    // 부모 클래스
    class Animal {
      constructor(name, habitat) {
        this.name = name;
        this.habitat = habitat;
        this.energy = 100;
      }
      
      eat(food) {
        this.energy += 20;
        return `${this.name}가 ${food}를 먹었습니다`;
      }
      
      sleep() {
        this.energy = 100;
        return `${this.name}가 잠을 잡니다`;
      }
      
      static getKingdom() {
        return "Animalia";
      }
    }
    
    // 자식 클래스
    class Dog extends Animal {
      constructor(name, habitat, breed) {
        super(name, habitat);  // 부모 constructor 호출
        this.breed = breed;
        this.loyalty = 100;
      }
      
      bark() {
        this.energy -= 10;
        return `${this.name}가 멍멍!`;
      }
      
      wagTail() {
        this.loyalty += 5;
        return `${this.name}가 꼬리를 흔듭니다`;
      }
      
      // 부모 메소드 오버라이드
      eat(food) {
        const result = super.eat(food);  // 부모 메소드 호출
        
        if (food === "뼈다귀") {
          this.energy += 10;
          return result + " (뼈다귀 보너스!)";
        }
        return result;
      }
      
      // 정적 메소드도 상속 가능
      static getSpecies() {
        return "Canis lupus";
      }
    }
    

    4-2. 깊은 상속 구조

    class GermanShepherd extends Dog {
      constructor(name, habitat) {
        super(name, habitat, "German Shepherd");
        this.trainedSkills = [];
      }
      
      guard() {
        this.energy -= 15;
        return `${this.name}가 경비를 섭니다`;
      }
      
      learn(skill) {
        this.trainedSkills.push(skill);
        return `${this.name}가 ${skill}을 배웠습니다`;
      }
      
      // 조부모 메소드까지 접근
      eat(food) {
        if (food === "전용사료") {
          // 할아버지(Animal) 메소드 직접 호출은 불가능
          // 하지만 부모(Dog) 메소드를 통해 간접 접근
          const result = super.eat(food);
          this.trainedSkills.forEach(skill => {
            this.energy += 5; // 훈련된 스킬당 보너스
          });
          return result + ` (${this.trainedSkills.length}개 스킬 보너스!)`;
        }
        return super.eat(food);
      }
    }
    

    🔍 5. 두 방식의 상세 비교

    5-1. 문법적 차이점

    // ========== Prototype 방식 ==========
    function Animal(name) {
      this.name = name;
    }
    
    function Dog(name, breed) {
      Animal.call(this, name);      // 부모 생성자 호출
      this.breed = breed;
    }
    
    Dog.prototype = Object.create(Animal.prototype);  // 상속 설정
    Dog.prototype.constructor = Dog;                   // constructor 복구
    
    Dog.prototype.bark = function() {                  // 메소드 추가
      return `${this.name} barks`;
    };
    
    Dog.prototype.speak = function() {                 // 오버라이드
      return Animal.prototype.speak.call(this) + " woof!";
    };
    
    // ========== Class 방식 ==========
    class Animal {
      constructor(name) {
        this.name = name;
      }
    }
    
    class Dog extends Animal {        // extends로 상속
      constructor(name, breed) {
        super(name);                  // super로 부모 호출
        this.breed = breed;
      }
      
      bark() {                        // 메소드 추가
        return `${this.name} barks`;
      }
      
      speak() {                       // 오버라이드
        return super.speak() + " woof!";
      }
    }
    

    5-2. 실행 시점과 동작

    // 두 방식 모두 동일한 prototype chain 생성
    const prototypeDog = new PrototypeDog("바둑이", "진돗개");
    const classDog = new ClassDog("멍멍이", "골든리트리버");
    
    // 내부 구조 동일
    console.log(prototypeDog.__proto__ === PrototypeDog.prototype); // true
    console.log(classDog.__proto__ === ClassDog.prototype);         // true
    
    // instanceof 결과도 동일
    console.log(prototypeDog instanceof PrototypeDog);  // true
    console.log(prototypeDog instanceof Animal);        // true
    console.log(classDog instanceof ClassDog);          // true
    console.log(classDog instanceof Animal);            // true
    

    5-3. 장단점 비교

    Prototype 방식:

    • ✅ 더 명시적, 내부 동작 이해하기 좋음
    • ✅ 메모리 사용량 조금 더 효율적
    • ✅ 동적으로 prototype 수정 가능
    • ❌ 문법이 복잡하고 실수하기 쉬움
    • ❌ constructor 복구 등 보일러플레이트 많음

    Class 방식:

    • ✅ 직관적이고 다른 언어와 유사
    • ✅ 실수할 여지가 적음
    • ✅ super 키워드로 부모 접근 쉬움
    • ✅ 정적 메소드, getter/setter 등 추가 기능
    • ❌ 내부 동작이 숨겨져 있음
    • ❌ hoisting이 되지 않음

    🎯 6. 실무에서의 사용법과 권장사항

    6-1. 현대적 사용 패턴

    // 현대적 Class 사용법
    class ApiClient {
      constructor(baseUrl, apiKey) {
        this.baseUrl = baseUrl;
        this.apiKey = apiKey;
      }
      
      async get(endpoint) {
        const response = await fetch(`${this.baseUrl}${endpoint}`, {
          headers: { 'Authorization': `Bearer ${this.apiKey}` }
        });
        return response.json();
      }
    }
    
    class UserApiClient extends ApiClient {
      constructor(baseUrl, apiKey) {
        super(baseUrl, apiKey);
      }
      
      async getUser(id) {
        return this.get(`/users/${id}`);
      }
      
      async updateUser(id, data) {
        const response = await fetch(`${this.baseUrl}/users/${id}`, {
          method: 'PUT',
          headers: { 
            'Authorization': `Bearer ${this.apiKey}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(data)
        });
        return response.json();
      }
    }
    

    6-2. 권장사항과 베스트 프랙티스

    ✅ 추천:

    • 새로운 프로젝트에서는 Class 문법 사용
    • 상속보다는 Composition 패턴 고려
    • private 필드 사용 (최신 브라우저)
    class BankAccount {
      #balance = 0;  // private 필드
      
      constructor(owner) {
        this.owner = owner;
      }
      
      deposit(amount) {
        this.#balance += amount;
        return this.#balance;
      }
      
      getBalance() {
        return this.#balance;
      }
    }
    

    ❌ 주의사항:

    • 너무 깊은 상속 구조는 피하기
    • Prototype 직접 수정은 신중하게
    • Class와 Function 생성자 혼용 피하기

    🔚 마무리

    JavaScript의 상속은 Prototype 기반이지만, Class 문법으로 더 쉽게 사용할 수 있다.

    핵심 이해:

    1. 모든 것은 Prototype Chain – Class도 내부적으로는 Prototype 사용
    2. 상속 = Prototype Chain 연결 – 부모의 속성과 메소드에 접근 가능
    3. 오버라이드 = 자식에서 재정의 – super로 부모 기능 활용 가능
    4. new 키워드가 모든 마법을 만듦 – 객체 생성과 prototype 연결

    실무에서는 Class 문법을 사용하되, Prototype의 원리를 이해하고 있으면 JavaScript를 더 깊이 있게 활용할 수 있다.

  • JS의 객체 지향

    JavaScript에서 거의 모든 것이 객체이다.

    JavaScript의 특이한 점:

    • 배열, 함수, 심지어 Date, RegExp 등도 모두 객체
    • 하지만 원시 타입은 객체가 아님: number, string, boolean, null, undefined

    객체 지향 방법들:

    1. 클래스 방식 (ES6+, 최신):

    class Counter {
        constructor(initialValue = 0) {
            this.count = initialValue;
            this.history = [];
        }
        
        increment() {
            this.count++;
            this.addToHistory("+1");
        }
        
        decrement() {
            this.count--;
            this.addToHistory("-1");
        }
        
        addToHistory(action) {
            this.history.push({
                action: action,
                result: this.count,
                time: new Date().toLocaleTimeString()
            });
        }
    }
    
    // 사용
    const myCounter = new Counter(0);
    myCounter.increment();
    console.log(myCounter.count);  // 1
    

    2. 생성자 함수 방식 (예전 방식):

    function Counter(initialValue) {
        this.count = initialValue || 0;
        this.history = [];
    }
    
    Counter.prototype.increment = function() {
        this.count++;
        this.addToHistory("+1");
    };
    
    Counter.prototype.addToHistory = function(action) {
        this.history.push({
            action: action,
            result: this.count,
            time: new Date().toLocaleTimeString()
        });
    };
    

    3. 객체 리터럴 방식:

    const counterFactory = {
        create(initialValue = 0) {
            return {
                count: initialValue,
                history: [],
                increment() {
                    this.count++;
                    this.addToHistory("+1");
                },
                addToHistory(action) {
                    this.history.push({
                        action: action,
                        result: this.count,
                        time: new Date().toLocaleTimeString()
                    });
                }
            };
        }
    };
    

    상속도 가능:

    class AdvancedCounter extends Counter {
        constructor(initialValue, maxValue) {
            super(initialValue);
            this.maxValue = maxValue;
        }
        
        increment() {
            if (this.count < this.maxValue) {
                super.increment();
            }
        }
    }
  • 6월 19일 17시까지 배운 것들

    HTML/CSS 기초

    • div 태그와 컨테이너 개념
    • class vs id 차이점 (.class, #id)
    • CSS 속성들:
      • display: flex (flexbox 레이아웃)
      • justify-content, align-items (정렬)
      • padding, border-radius, box-shadow
      • vh 단위 (viewport height)
    • CSS 우선순위와 !important

    JavaScript 기초 문법

    • 변수: let vs const vs var
    • 함수 정의와 호출
    • 조건문: if/else, 비교 연산자 (=== vs ==)
    • DOM 조작:
      • document.querySelector(), getElementById()
      • addEventListener(), innerHTML, textContent
      • createElement(), appendChild()

    JavaScript 고급 개념

    • 배열과 메소드들:
      • push(), slice(), reverse(), forEach()
    • 객체 리터럴: {} 문법과 키-값 쌍
    • 화살표 함수: item => { ... }
    • 함수를 값으로 취급 (고차 함수 개념)
    • 객체 속성 단축 문법: { action, result }

    객체 지향 프로그래밍

    • class 문법과 constructor
    • this 키워드 (현재 객체 참조)
    • new 키워드로 인스턴스 생성
    • super 키워드 (상속에서 부모 접근)
    • 메소드와 속성의 캡슐화

    브라우저/DOM 개념

    • 이벤트 처리 (onclick, keydown)
    • window 객체와 전역 스코프
    • 스코프 차이 (함수 스코프 vs 블록 스코프)

    실습 프로젝트

    • 인터랙티브 카운터 앱 제작:
      • 버튼 클릭 이벤트
      • 키보드 입력 처리
      • 동적 HTML 생성
      • 데이터 기록과 표시
      • 함수형 → 객체 지향 리팩토링
  • JS에서 원시타입과 객체의 차이

    원시타입 (Primitive Types):

    javascriptconst num = 5;           // number
    const text = "hello";    // string  
    const flag = true;       // boolean
    const empty = null;      // null
    const nothing = undefined; // undefined

    객체 타입 (Object Types):

    javascriptconst arr = [1, 2, 3];          // Array (객체)
    const obj = { name: "김철수" };  // Object
    const func = function() {};     // Function (객체)
    const date = new Date();        // Date (객체)

    차이점:

    1. 메소드 호출:

    javascript// 원시타입인데 메소드가 있는 것처럼 보임 (JavaScript 마법!)
    const str = "hello";
    console.log(str.toUpperCase()); // "HELLO"
    // → JavaScript가 임시로 String 객체를 만들어서 메소드 실행
    
    // 진짜 객체는 메소드가 실제로 있음
    const arr = [1, 2, 3];
    arr.push(4);  // 진짜 객체의 메소드

    2. 저장 방식:

    javascript// 원시타입 = 값 자체가 저장됨
    let a = 5;
    let b = a;    // 5라는 값이 복사됨
    a = 10;
    console.log(b); // 5 (영향 없음)
    
    // 객체 = 참조(주소)가 저장됨  
    let obj1 = { count: 5 };
    let obj2 = obj1;    // 같은 객체를 가리킴
    obj1.count = 10;
    console.log(obj2.count); // 10 (영향 받음!)

    3. typeof 결과:

    javascriptconsole.log(typeof 5);           // "number"
    console.log(typeof "hello");     // "string"
    console.log(typeof true);        // "boolean"
    
    console.log(typeof [1,2,3]);     // "object"
    console.log(typeof {name: "김철수"}); // "object"
    console.log(typeof function(){}); // "function" (특별한 객체)

    정리:

    객체: 참조, 가변, 공유됨

    원시타입: 값 자체, 불변, 복사됨