Author: 짠밍

  • 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" (특별한 객체)

    정리:

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

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

  • 가장 넓은 길

    양광모

    살다 보면
    길이 보이지 않을 때가 있다
    원망하지 말고 기다려라

    눈에 덮였다고
    길이 없어진 것이 아니요
    어둠에 묻혔다고
    길이 사라진 것도 아니다

    묵묵히 빗자루를 들고
    눈을 치우다 보면
    새벽과 함께
    길이 나타날 것이다

    가장 넓은 길은
    언제나 내 마음속에 있다

  • 어느날 고궁을 나오면서

    김수영

    왜 나는 조그마한 일에만 분개하는가
    저 왕궁 대신에 왕궁의 음탕 대신에
    50원짜리 갈비가 기름덩어리만 나왔다고 분개하고
    옹졸하게 분개하고 설렁탕집 돼지 같은 주인년한테 욕을 하고
    옹졸하게 욕을 하고

    한번 정정당당하게
    붙잡혀간 소설가를 위하여
    언론의 자유를 요구하고 월남 파병에 반대하는
    자유를 이행하지 못하고
    20원을 받으러 세 번씩 네 번씩
    찾아오는 야경꾼들만 증오하고 있는가

    옹졸한 나의 전통은 유구하고 이제 내 앞에
    정서로 가로놓여 있다
    이를테면 이런 일이 있었다
    부산에 포로수용소의 제 14야전병원에 있을 때
    정보원이 너어스들과 스펀지를 만들고 거즈를
    개키고 있는 나를 보고 포로경찰이 되지 않는다고
    남자가 뭐 이런 일을 하고 있느냐고 놀린 일이 있었다
    너어스들 옆에서

    지금도 내가 반항하고 있는 것은 이 스펀지 만들기와
    거즈 접고 있는 일과 조금도 다름없다
    개의 울음소리를 듣고 그 비명에 지고
    머리에 피도 안 마른 애놈의 투정에 진다
    떨어지는 은행나무잎도 내가 밟고 가는 가시밭

    아무래도 나는 비켜서 있다 절정 위에는 서 있지
    않고 암만해도 조금쯤 옆으로 비켜서 있다
    그리고 조금쯤 옆에 서 있는 것이 조금쯤
    비겁한 것이라고 알고 있다!

    그러니까 이렇게 옹졸하게 반항한다
    이발쟁이에게
    땅주인에게는 못하고 이발쟁이에게
    구청 직원에게는 못하고 동회 직원에게도 못하고
    야경꾼에게 20원 때문에 10원 때문에 1원때문에
    우습지 않느냐 1원 때문에

    모래야 나는 얼마큼 작으냐
    바람아 먼지야 풀아 나는 얼마큼 작으냐
    정말 얼마큼 작으냐…

  • 소망

    나태주

    가을은 하늘을 우러러
    보아야 하는 시절

    거기 네가 있었음 좋겠다

    맑은 웃음 머금은
    네가 있었음 좋겠다.

    시집 “마음이 살짝 기운다” 中