Learning
레슨 10 / 12·5개 토픽

프로토타입과 클래스

프로토타입 체인

JavaScript의 모든 객체는 숨겨진 [[Prototype]] 링크를 통해 다른 객체와 연결됩니다. 속성이나 메서드를 찾을 때 현재 객체에 없으면 프로토타입 체인을 따라 상위 객체에서 탐색합니다. 이것이 JavaScript 상속의 핵심 메커니즘입니다.

javascript
// Object.create()로 프로토타입 체인 구성
const animal = {
  type: "동물",
  speak() {
    return this.name + "이(가) 소리를 냅니다.";
  },
};

const dog = Object.create(animal);
dog.name = "바둑이";
dog.bark = function() {
  return this.name + ": 멍멍!";
};

console.log(dog.bark());   // "바둑이: 멍멍!"
console.log(dog.speak());  // "바둑이이(가) 소리를 냅니다." (animal에서 상속)
console.log(dog.type);     // "동물" (프로토타입 체인)

// 프로토타입 체인 확인
console.log(Object.getPrototypeOf(dog) === animal); // true
console.log(dog.hasOwnProperty("name"));  // true
console.log(dog.hasOwnProperty("type"));  // false (상속된 속성)

생성자 함수와 prototype

javascript
// ES6 이전의 전통적인 생성자 패턴
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// prototype에 메서드 추가 — 모든 인스턴스가 공유
Person.prototype.greet = function() {
  return "안녕하세요, " + this.name + "입니다. (" + this.age + "세)";
};

Person.prototype.isAdult = function() {
  return this.age >= 18;
};

const kim = new Person("김철수", 25);
const lee = new Person("이영희", 17);

console.log(kim.greet());    // "안녕하세요, 김철수입니다. (25세)"
console.log(lee.isAdult());  // false

// 같은 prototype 메서드를 공유
console.log(kim.greet === lee.greet); // true

ES6 클래스 문법

ES6 클래스는 프로토타입 기반 패턴을 깔끔하게 감싼 문법적 설탕(syntactic sugar)입니다. constructor, 인스턴스 메서드, static 메서드, 접근자(getter/setter)를 정의할 수 있습니다.

javascript
class Product {
  // private 필드 (# 접두사)
  #discount;

  constructor(name, price) {
    this.name = name;
    this.price = price;
    this.#discount = 0;
  }

  // 인스턴스 메서드
  getInfo() {
    return this.name + " — " + this.finalPrice + "원";
  }

  // getter — 속성처럼 접근
  get finalPrice() {
    return this.price * (1 - this.#discount);
  }

  // setter
  set discountRate(rate) {
    if (rate < 0 || rate > 1) throw new Error("0~1 사이 값");
    this.#discount = rate;
  }

  // static 메서드 — 인스턴스 없이 호출
  static compare(a, b) {
    return a.price - b.price;
  }
}

const laptop = new Product("노트북", 1200000);
laptop.discountRate = 0.1;
console.log(laptop.getInfo());     // "노트북 — 1080000원"
console.log(laptop.finalPrice);    // 1080000

const phone = new Product("스마트폰", 800000);
console.log(Product.compare(phone, laptop)); // -400000

상속: extends와 super

javascript
class Shape {
  constructor(color) {
    this.color = color;
  }

  describe() {
    return this.color + " " + this.constructor.name;
  }

  area() {
    throw new Error("하위 클래스에서 구현하세요");
  }
}

class Circle extends Shape {
  constructor(color, radius) {
    super(color);  // 부모 constructor 호출 (필수)
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius ** 2;
  }
}

class Rectangle extends Shape {
  constructor(color, width, height) {
    super(color);
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }
}

const circle = new Circle("빨간", 5);
console.log(circle.describe()); // "빨간 Circle"
console.log(circle.area());    // 78.539...

const rect = new Rectangle("파란", 4, 6);
console.log(rect.area());      // 24

// instanceof로 타입 확인
console.log(circle instanceof Circle); // true
console.log(circle instanceof Shape);  // true
console.log(rect instanceof Circle);   // false

프로토타입 vs 클래스 비교

  • class는 프로토타입의 문법적 설탕 — 내부적으로 동일하게 동작
  • class는 호이스팅되지 않음 — 선언 전에 사용하면 ReferenceError
  • class 내부는 자동으로 strict mode 적용
  • static 메서드는 Constructor.method()로 직접 정의한 것과 동일
  • # private 필드는 클래스 문법에서만 지원 (프로토타입 패턴에서는 클로저로 구현)
  • instanceof는 프로토타입 체인을 따라 확인 — 두 방식 모두 동일하게 동작
💡

실무에서는 가독성과 유지보수를 위해 ES6 클래스 문법을 사용하는 것이 권장됩니다. 다만 프로토타입 체인을 이해해야 디버깅이나 라이브러리 코드 분석이 수월합니다.