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), 믹스인을 비교하고, 언제 무엇을 선택해야 하는지 정리합니다.