Frontend/Javascript Essentials

프로토타입 기반 상속

JTB 2025. 10. 12. 17:09

 

자바스크립트는 다른 언어(C++, Java 등)의 클래스 기반 상속(class-based inheritance)과 달리,

프로토타입 기반 상속(prototype-based inheritance)을 사용한다.

이는 객체가 다른 객체를 직접 상속받아 동작하는 방식으로, 자바스크립트의 유연한 객체 지향 특성을 잘 보여준다.

 

1. 프로토타입 기반 상속이란?

모든 객체는 내부적으로 [[Prototype]](또는 __proto__)이라는 숨겨진 참조를 가진다.

이 참조를 통해 상위 객체(프로토타입 객체)의 속성과 메서드를 공유받는다. 즉, 객체 간 직접적인 상속 관계가 형성되는 것이다.

 

ES6의 classextends 문법은 이러한 프로토타입 상속을 문법적으로 단순화한 표현(syntactic sugar)이다.

 

 

2. 동작 원리: 프로토타입 체이닝

  1. 객체가 어떤 속성이나 메서드를 호출하면, 자바스크립트 엔진은 먼저 자신의 프로퍼티에서 찾는다.
  2. 없다면 [[Prototype]]을 따라 상위 객체에서 같은 이름을 탐색한다.
  3. 이 과정을 프로토타입 체이닝(Prototype Chaining)이라 하며, 상위 객체에서도 찾지 못하면 최종적으로 null에서 탐색을 멈추고 undefined를 반환한다.
const obj = {};
console.log(obj.toString()); // [object Object]
// obj에는 toString이 없지만, Object.prototype에서 상속받아 사용

 

3. 프로토타입 상속의 장점

  1. 공통 기능 재사용 - 예: Array, Object, Function 등 내장 객체의 공통 메서드는 각자의 프로토타입에 정의되어, 모든 인스턴스가 이를 공유한다.
  2. 메모리 효율성 - 인스턴스마다 메서드를 복제하지 않고 공통 프로토타입을 참조하므로 메모리 낭비가 없다.
  3. 동적 확장성 - 런타임 중에도 프로토타입 체인을 변경하거나 메서드를 추가할 수 있어 매우 유연하다.

 

4. 프로토타입 상속 예제

자바스크립트는 함수가 정의될 때 자동으로 prototype 객체를 생성한다.

prototype 객체 안에는 constructor 속성이 있으며, 이를 통해 다시 원래 생성자 함수를 가리킨다.

 

이 구조를 이용해 상속을 구현할 수 있다.

1. 기본 구조

function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function () {
  return `${this.name} makes a sound.`;
};

 

Animal은 생성자 함수다. Animal.prototypespeak 메서드를 정의하면, 모든 Animal 인스턴스가 이를 공유한다.

const cat = new Animal('Kitty');
console.log(cat.speak()); // Kitty makes a sound.

이 시점에서 cat 객체 내부 구조는 다음과 유사하다.

cat
 ├── name: "Kitty"
 └── [[Prototype]] → Animal.prototype
                           └── speak()

즉, cat.speak()를 호출할 때

cat 자신에게 speak가 없으므로, JS 엔진은 [[Prototype]]을 따라

Animal.prototype에서 speak를 찾아 실행한다.

2. 상속 구현 (Dog → Animal)

이제 Dog가 Animal을 상속받도록 만들어보자.

function Dog(name) {
  // Animal의 생성자 호출
  Animal.call(this, name);
}

여기서 Animal.call(this, name)Dog 생성자 내부에서 Animal의 초기화 로직을 재사용하는 부분이다.

 

이제 프로토타입 체인을 연결해야 한다.

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Object.create(Animal.prototype) → 새로운 객체를 만들되, 그 객체의 [[Prototype]]Animal.prototype으로 지정한다.

Dog.prototype.constructor = Dog → 프로토타입 체인을 변경했기 때문에, constructor가 다시 Animal을 가리키는 문제를 수정한다.

3. 메서드 오버라이딩

Dog만의 메서드를 추가할 수 있다.

Dog.prototype.speak = function () {
  return `${this.name} barks.`;
};

4. 최종 예제

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  return `${this.name} makes a sound.`;
};

function Dog(name) {
  Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.speak = function () {
  return `${this.name} barks.`;
};

const dog = new Dog('Buddy');

console.log(dog.speak()); // Buddy barks.
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true

5. 실행 흐름 정리

  1. new Dog('Buddy')를 실행하면
    • 새로운 객체가 생성되고 this로 바인딩된다.
    • Animal.call(this, name)을 통해 name 속성이 설정된다.
  2. JS 엔진은 dog.speak() 호출 시,
    • Dog.prototype에서 speak를 찾고(Buddy barks. 출력)
    • 없으면 Animal.prototype로 올라간다.
  3. 이때 dog의 내부 구조는 아래와 같다:
dog
 ├── name: "Buddy"
 └── [[Prototype]] → Dog.prototype
                           ├── speak()  // Dog의 speak
                           └── [[Prototype]] → Animal.prototype
                                                      ├── speak()
                                                      └── constructor: Animal

6. 프로토타입 체이닝의 확인

console.log(Object.getPrototypeOf(dog) === Dog.prototype); // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true
console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null

이처럼 프로토타입은 계층적으로 연결되어 있고, null에 도달하면 탐색이 종료된다.

 

5. 정리

개념 설명
상속 방식 프로토타입 기반 (객체 간 상속)
핵심 메커니즘 [[Prototype]] → 프로토타입 체이닝
장점 메모리 효율, 재사용성, 유연성
ES6 클래스 문법 프로토타입 기반 상속을 단순화한 문법적 설탕