Ray Book
프로토타입과 상속

클래스의 실체

ES2015 class는 프로토타입의 문법적 설탕, class, extends, super, static의 내부 동작을 시각화합니다

javascriptclassprototypeextendssuper

class는 함수다

class Dog {
  constructor(name) {
    this.name = name;
  }
  bark() {
    return this.name + " barks!";
  }
}

typeof Dog; // "function"

class는 새로운 타입이 아닙니다. 함수입니다. 이전 글의 생성자 함수 + prototype 패턴의 문법적 설탕 (syntactic sugar) 입니다.

// class 문법
class Dog {
  constructor(name) { this.name = name; }
  bark() { return this.name + " barks!"; }
}

// 동등한 생성자 함수 패턴
function Dog(name) { this.name = name; }
Dog.prototype.bark = function() {
  return this.name + " barks!";
};

두 코드는 거의 동일한 결과를 만듭니다.

"거의" 동일한 차이

class는 몇 가지 차이가 있습니다.

1. new 없이 호출 불가

function OldDog() {}
OldDog(); // 동작함 (좋지 않지만)

class NewDog {}
NewDog(); // TypeError: Class constructor NewDog
          // cannot be invoked without 'new'

2. 엄격 모드 강제

class Dog {
  bark() {
    // 여기는 자동으로 strict mode
    undeclaredVar = 1; // ReferenceError
  }
}

3. 열거 불가능

class Dog {
  bark() {}
}

// class 메서드는 열거되지 않음
Object.keys(Dog.prototype); // [], bark가 안 보임

// 생성자 함수 방식은 열거됨
function OldDog() {}
OldDog.prototype.bark = function() {};
Object.keys(OldDog.prototype); // ["bark"]

4. 호이스팅 차이

함수 선언은 완전히 호이스팅되지만, class 선언은 let/const처럼 TDZ(Temporal Dead Zone)에 놓여 선언 전에 사용할 수 없습니다.

// 함수 선언, 호이스팅됨
const dog = new OldDog(); // 동작함
function OldDog() {}

// class 선언, TDZ
const dog2 = new NewDog(); // ReferenceError!
class NewDog {}

extends, 상속

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return this.name + " speaks";
  }
}

class Dog extends Animal {
  bark() {
    return this.name + " barks";
  }
}

const d = new Dog("Rex");
d.bark();  // "Rex barks" , Dog.prototype
d.speak(); // "Rex speaks", Animal.prototype
코드
1class Animal {
2 constructor(name) {
3 this.name = name;
4 }
5 speak() {
6 return this.name + " speaks";
7 }
8}
9 
10class Dog extends Animal {
11 bark() {
12 return this.name + " barks";
13 }
14}
15 
16const d = new Dog("Rex");
프로토타입 체인
Animal (함수)
prototype→ Animal.prototype
Animal.prototype
constructor→ Animal
speakfunction
class Animal은 내부적으로 함수입니다. typeof Animal === "function". 메서드(speak)는 Animal.prototype에 들어갑니다. 생성자 함수 + prototype과 동일한 구조입니다.

extends가 설정하는 두 개의 링크

extends는 프로토타입 링크를 두 개 설정합니다.

// 1. 인스턴스 메서드 상속
Object.getPrototypeOf(Dog.prototype) === Animal.prototype; // true
// d.speak()를 찾을 때: d → Dog.prototype → Animal.prototype

// 2. 정적 메서드 상속
Object.getPrototypeOf(Dog) === Animal; // true
// Dog.create()를 찾을 때: Dog → Animal

이전 글의 생성자 함수로 이것을 수동 구현하면:

function Animal(name) { this.name = name; }
Animal.prototype.speak = function() {
  return this.name + " speaks";
};

function Dog(name) { Animal.call(this, name); }

// extends가 하는 일
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Object.setPrototypeOf(Dog, Animal);

Dog.prototype.bark = function() {
  return this.name + " barks";
};

class extends가 이 모든 과정을 한 줄로 처리합니다.

super

super는 두 가지 용도로 쓰입니다.

super(), 부모 생성자 호출

class Dog extends Animal {
  constructor(name, breed) {
    super(name);     // Animal의 constructor 호출
    this.breed = breed;
  }
}

extends를 사용하면 자식 클래스의 constructor에서 반드시 super()를 먼저 호출해야 합니다. this를 사용하기 전에 부모가 초기화해야 하기 때문입니다.

class Dog extends Animal {
  constructor(name) {
    this.breed = "unknown"; // ReferenceError!
    super(name);            // super() 전에 this 사용 불가
  }
}

super.method(), 부모 메서드 호출

class Dog extends Animal {
  speak() {
    const parentResult = super.speak();
    return parentResult + " and barks";
  }
}

const d = new Dog("Rex");
d.speak(); // "Rex speaks and barks"

super.speak()Animal.prototype.speakthis를 바인딩하여 호출합니다.

static, 정적 메서드

class Dog {
  constructor(name) {
    this.name = name;
  }

  // 인스턴스 메서드, Dog.prototype에 저장
  bark() {
    return this.name + " barks";
  }

  // 정적 메서드, Dog 자체에 저장
  static create(name) {
    return new Dog(name);
  }
}

const d = Dog.create("Rex"); // Dog.create(), OK
d.bark();                     // d.bark(), OK
d.create("Buddy");            // TypeError, 인스턴스에서는 접근 불가

정적 메서드는 Dog.prototype이 아닌 Dog 함수 자체에 저장됩니다. 인스턴스에서는 접근할 수 없습니다.

private 필드

class Dog {
  #name; // private 필드

  constructor(name) {
    this.#name = name;
  }

  get name() {
    return this.#name;
  }
}

const d = new Dog("Rex");
d.name;   // "Rex" (getter)
d.#name;  // SyntaxError, 외부 접근 불가

# 접두사로 선언한 필드는 클래스 바깥에서 접근할 수 없습니다. 프로토타입이 아닌 각 인스턴스에 직접 저장됩니다.

class 문법은 언제 쓰는가

상황추천
상태 + 메서드가 있는 객체를 여러 개 만들 때class
상속 관계가 필요할 때class extends
단순 유틸리티 함수 모음일반 함수 / 모듈
설정 객체, 옵션객체 리터럴

class는 프로토타입을 더 읽기 쉽고 안전하게 사용하는 문법입니다. 하지만 내부에서는 여전히 프로토타입 체인이 동작합니다.

다음 단계

class의 실체를 이해했습니다. 마지막 글에서는 실전에서 쓰이는 상속 패턴, 프로토타입 상속, 조합 (composition), 믹스인을 비교하고, 언제 무엇을 선택해야 하는지 정리합니다.