자바스크립트는 다른 언어(C++, Java 등)의 클래스 기반 상속(class-based inheritance)과 달리,
프로토타입 기반 상속(prototype-based inheritance)을 사용한다.
이는 객체가 다른 객체를 직접 상속받아 동작하는 방식으로, 자바스크립트의 유연한 객체 지향 특성을 잘 보여준다.
1. 프로토타입 기반 상속이란?
모든 객체는 내부적으로 [[Prototype]](또는 __proto__)이라는 숨겨진 참조를 가진다.
이 참조를 통해 상위 객체(프로토타입 객체)의 속성과 메서드를 공유받는다. 즉, 객체 간 직접적인 상속 관계가 형성되는 것이다.
ES6의 class와 extends 문법은 이러한 프로토타입 상속을 문법적으로 단순화한 표현(syntactic sugar)이다.
2. 동작 원리: 프로토타입 체이닝
- 객체가 어떤 속성이나 메서드를 호출하면, 자바스크립트 엔진은 먼저 자신의 프로퍼티에서 찾는다.
- 없다면 [[Prototype]]을 따라 상위 객체에서 같은 이름을 탐색한다.
- 이 과정을 프로토타입 체이닝(Prototype Chaining)이라 하며, 상위 객체에서도 찾지 못하면 최종적으로 null에서 탐색을 멈추고 undefined를 반환한다.
const obj = {};
console.log(obj.toString()); // [object Object]
// obj에는 toString이 없지만, Object.prototype에서 상속받아 사용
3. 프로토타입 상속의 장점
- 공통 기능 재사용 - 예: Array, Object, Function 등 내장 객체의 공통 메서드는 각자의 프로토타입에 정의되어, 모든 인스턴스가 이를 공유한다.
- 메모리 효율성 - 인스턴스마다 메서드를 복제하지 않고 공통 프로토타입을 참조하므로 메모리 낭비가 없다.
- 동적 확장성 - 런타임 중에도 프로토타입 체인을 변경하거나 메서드를 추가할 수 있어 매우 유연하다.
4. 프로토타입 상속 예제
자바스크립트는 함수가 정의될 때 자동으로 prototype 객체를 생성한다.
이 prototype 객체 안에는 constructor 속성이 있으며, 이를 통해 다시 원래 생성자 함수를 가리킨다.
이 구조를 이용해 상속을 구현할 수 있다.
1. 기본 구조
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
return `${this.name} makes a sound.`;
};
Animal은 생성자 함수다. Animal.prototype에 speak 메서드를 정의하면, 모든 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. 실행 흐름 정리
- new Dog('Buddy')를 실행하면
- 새로운 객체가 생성되고 this로 바인딩된다.
- Animal.call(this, name)을 통해 name 속성이 설정된다.
- JS 엔진은 dog.speak() 호출 시,
- Dog.prototype에서 speak를 찾고(Buddy barks. 출력)
- 없으면 Animal.prototype로 올라간다.
- 이때 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 클래스 문법 | 프로토타입 기반 상속을 단순화한 문법적 설탕 |
'Frontend > Javascript Essentials' 카테고리의 다른 글
클로저 — 함수가 변수를 기억하는 방식 (0) | 2025.10.12 |
---|---|
이벤트 루프 (0) | 2025.10.11 |