자바스크립트 프로토타입, 정말 이해하고 계신가요?
class 키워드에 익숙해진 요즘, 프로토타입은 "예전에 쓰던 문법" 정도로 여겨지곤 합니다. 하지만 자바스크립트 엔진 내부에서는 지금 이 순간에도 프로토타입 체인이 쉼 없이 동작하고 있습니다. 면접에서 유독 이 주제가 자주 나오는 이유도 여기에 있습니다 — "자바스크립트를 얼마나 깊이 이해하고 있는가"를 가장 효율적으로 확인할 수 있는 질문이기 때문입니다.
이 글에서는 프로토타입이 왜 존재하는지부터, 프로토타입 체인이 실제로 어떻게 동작하는지, 그리고 class 문법이 이것을 어떻게 감싸고 있는지까지 순서대로 정리해보겠습니다.
1. 프로토타입은 왜 필요했을까
자바스크립트에는 원래 클래스가 없었습니다. 대신 객체가 다른 객체를 참조해서 속성과 메서드를 "빌려 쓰는" 방식을 택했는데, 이게 바로 프로토타입 기반 상속입니다.
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`안녕하세요, ${this.name}입니다.`);
};
const kim = new Person("김철수");
kim.sayHello(); // "안녕하세요, 김철수입니다."
여기서 kim 객체 자체에는 sayHello라는 메서드가 없습니다. 그런데도 호출이 됩니다. 왜일까요?
kim.sayHello()를 호출하면 자바스크립트 엔진은 다음 순서로 탐색합니다.
- kim 객체 자신에게 sayHello가 있는지 확인
- 없다면 kim의 프로토타입(Person.prototype)에서 확인
- 거기에도 없다면 그 프로토타입의 프로토타입으로 계속 이동
- Object.prototype까지 가서도 없으면 undefined
이 연결 고리가 바로 프로토타입 체인입니다.
2. prototype과 __proto__, 헷갈리는 두 단어 정리
이 두 단어를 혼용해서 쓰다가 개념이 꼬이는 경우가 정말 많습니다. 역할이 완전히 다릅니다.
구분 역할 존재하는 대상
| prototype | 함수가 new로 생성할 인스턴스에게 물려줄 "설계도" | 함수(정확히는 생성자 함수)에만 존재 |
| __proto__ | 실제로 인스턴스가 참조하고 있는 프로토타입 객체 | 모든 객체에 존재 |
console.log(kim.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
즉, kim.__proto__는 Person.prototype을 가리키는 실제 연결 통로이고, Person.prototype은 그 통로 끝에 있는 객체입니다. 참고로 __proto__는 표준화 이전부터 쓰이던 관습적 프로퍼티라, 요즘은 Object.getPrototypeOf(kim)을 쓰는 것이 권장됩니다.
3. 프로토타입 체인을 눈으로 그려보면
kim
└─ __proto__ → Person.prototype
├─ sayHello()
└─ __proto__ → Object.prototype
├─ toString()
├─ hasOwnProperty()
└─ __proto__ → null
체인의 끝은 항상 null입니다. 여기까지 가도 못 찾으면 자바스크립트는 더 이상 찾을 곳이 없다고 판단하고 undefined를 반환합니다.
실무에서 자주 겪는 실수 하나를 예로 들어보겠습니다.
for (const key in kim) {
console.log(key);
}
// name, sayHello 둘 다 출력됨
for...in은 프로토타입 체인을 타고 올라가며 열거 가능한(enumerable) 모든 프로퍼티를 순회합니다. 그래서 kim 자신의 속성뿐 아니라 Person.prototype에 있는 sayHello까지 나옵니다. 배열이나 객체를 순회할 때 의도치 않게 상속받은 속성까지 딸려 나오는 버그는 대부분 이 지점에서 발생합니다. 이런 이유로 실무에서는 Object.keys()나 hasOwnProperty 체크를 함께 쓰는 경우가 많습니다.
4. class는 사실 프로토타입 위에 그려진 문법
ES6에서 등장한 class는 완전히 새로운 상속 모델이 아니라, 프로토타입 기반 상속을 좀 더 익숙한 형태로 표현한 **문법적 설탕(syntactic sugar)**입니다.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}이(가) 소리를 냅니다.`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name}이(가) 짖습니다.`);
}
}
const dog = new Dog("초코");
dog.speak(); // "초코이(가) 짖습니다."
이걸 내부적으로 풀어보면 다음과 동일합니다.
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
console.log(`${this.name}이(가) 소리를 냅니다.`);
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.speak = function () {
console.log(`${this.name}이(가) 짖습니다.`);
};
extends가 하는 일은 결국 Dog.prototype.__proto__를 Animal.prototype으로 연결해주는 것뿐입니다. class가 더 안전하고 읽기 쉬운 문법을 제공하는 건 맞지만, 그 아래에서 돌아가는 원리는 여전히 프로토타입 체인이라는 걸 이해하고 있으면 super, static, 오버라이딩 같은 개념도 훨씬 자연스럽게 받아들여집니다.
5. 왜 실무에서 이걸 알아야 할까
이론으로만 남겨두기 아까운 개념이라, 실제로 도움이 되는 순간들을 몇 가지 짚어보겠습니다.
메모리 효율: 메서드를 인스턴스마다 만들지 않고 프로토타입에 하나만 두면, 인스턴스가 수천 개 생성되어도 메서드는 메모리에 단 하나만 존재합니다. this.method = function() {...}처럼 생성자 내부에 메서드를 정의하면 이 이점을 잃게 됩니다.
디버깅: console.log로 객체를 찍었을 때 [[Prototype]]에 뭐가 걸려 있는지 이해하면, "이 메서드는 대체 어디서 온 거지?"라는 순간에 훨씬 빠르게 원인을 추적할 수 있습니다.
라이브러리 코드 읽기: 오래된 라이브러리나 폴리필 코드는 여전히 prototype을 직접 조작하는 경우가 많습니다. 이 개념 없이는 그런 코드를 읽어내기 어렵습니다.
6. 면접에서 자주 나오는 질문 정리
준비하시는 분들을 위해 실제로 자주 등장하는 질문 유형을 정리해봤습니다.
- prototype과 __proto__의 차이를 설명해달라
- 프로토타입 체인의 끝에는 무엇이 있는가
- class 문법이 내부적으로 어떻게 동작하는지 설명해달라
- Object.create()와 new 키워드의 차이는 무엇인가
- 프로토타입 오염(prototype pollution)이 무엇이고 왜 보안 이슈가 되는가
특히 마지막 질문은 최근 시니어 포지션 면접에서 자주 등장합니다. Object.prototype에 악의적인 속성을 주입해 애플리케이션 전역에 영향을 주는 공격 기법인데, 프로토타입 체인의 동작 원리를 모르면 이 개념 자체를 이해하기 어렵습니다.
마무리하며
프로토타입은 화려한 최신 문법은 아니지만, 자바스크립트라는 언어의 뼈대를 이루는 개념입니다. class, extends, super 같은 익숙한 키워드들도 결국 이 뼈대 위에서 동작한다는 걸 이해하고 나면, 코드를 읽는 시야 자체가 달라집니다.
다음 글에서는 이 개념을 확장해서 Object.create()를 활용한 순수 프로토타입 패턴과, 실무에서 자주 마주치는 프로토타입 오염 이슈를 좀 더 깊게 다뤄보겠습니다.
'Frontend' 카테고리의 다른 글
| [Frontend] 디바운싱과 쓰로틀링 (0) | 2026.07.15 |
|---|---|
| [Frontend] 프로토타입 상속 구현 (0) | 2026.07.15 |
| [Frontend] Redux 비동기 관리 정리 (0) | 2026.07.14 |
| [Frontend] Production에서 만나는 Edge Case (0) | 2026.07.14 |
| [Frontend] useTransition과 Error Boundary가 만나면? (0) | 2026.07.14 |