notes
19장. 프로토타입
모던 JS Deep Dive — 프로토타입 체인·상속과 객체 동작 핵심 정리.
참고: 이웅모 《모던 자바스크립트 Deep Dive》 19장 · [MDN Inheritance and the prototype chain](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)
JS 객체는 복사 상속이 아니라 위임 체인이다.
인스턴스에 없으면 Constructor.prototype으로, 없으면 Object.prototype으로 올라간다.
class만 외우면 “상속된다”에서 끝나는데, 디버깅할 때 드러나는 건 체인이다. 이 장이 그 체인의 전체 지도다.
flowchart BT
I[인스턴스] -->|[[Prototype]]| P[Ctor.prototype]
P -->|[[Prototype]]| O[Object.prototype]
O --> N[null]
19.1 객체지향 프로그래밍 — 내가 붙잡은 말
명령형 목록이 아니라 상태 + 행동을 객체로 묶고, 비슷한 객체가 행동을 공유하게 만드는 쪽. JS는 클래스 기반이 아니라 프로토타입 기반이 본체다. class는 그 위에 올린 친절한 문법(25장).
19.2 상속과 프로토타입
같은 메서드를 인스턴스마다 복사하면 메모리·수정 비용이 아프다. 프로토타입에 한 번 두고 위임한다.
“부모 객체의 능력을 빌린다”가 상속이고, 빌리는 길이 [[Prototype]] 링크다.
function Animal(name) {
this.name = name;
}
Animal.prototype.hello = function () {
return this.name;
};
const dog = new Animal('nana');
console.log(dog.hello()); // nana — 메서드는 프로토타입에
console.log(Object.getPrototypeOf(dog) === Animal.prototype); // true
19.3 프로토타입 객체
모든 객체는 [[Prototype]] 내부 슬롯을 갖는다. 그 슬롯이 가리키는 객체가 프로토타입.
함수(constructor)는 prototype 프로퍼티로 “인스턴스가 위임할 객체”를 들고 있다. 삼각형:
- 인스턴스 →
[[Prototype]]→Ctor.prototype Ctor.prototype.constructor→CtorCtor.prototype자체도 위로Object.prototype까지
19.3.1 __proto__ 접근자
레거시 창구. obj.__proto__로 [[Prototype]]을 읽·쓸 수 있는 환경이 많다.
진단용. 생성·연결은 Object.create / Object.getPrototypeOf / Object.setPrototypeOf(최후) / new·class.
19.3.2 함수의 prototype 프로퍼티
constructor만 가진다(17). new Foo() 시 인스턴스의 [[Prototype]]이 Foo.prototype을 가리킨다.
19.3.3 constructor 프로퍼티
Foo.prototype.constructor === Foo가 기본. 교체·직접 상속 때 깨질 수 있어, 필요하면 다시 맞춰 준다.
19.4 리터럴로 만든 객체의 생성자·프로토타입
{}로 만든 객체의 프로토타입은 Object.prototype. constructor는 Object.
배열 리터럴은 Array.prototype, 함수는 Function.prototype — 리터럴도 “어떤 생성자 계열인지”가 정해진다.
const o = {};
console.log(Object.getPrototypeOf(o) === Object.prototype); // true
console.log(o.constructor === Object); // true
19.5 프로토타입의 생성 시점
19.5.1 사용자 정의 생성자
함수 정의가 평가될 때 prototype 객체가 함께 준비된다. 그 안에 constructor가 연결된다.
19.5.2 빌트인 생성자
엔진 기동 시 Object/Array 등의 프로토타입이 이미 있다. 그래서 'hi'.toUpperCase()가 된다(21 래퍼 + String.prototype).
19.6 객체 생성 방식과 프로토타입 결정
생성 경로마다 위임 대상이 갈린다.
19.6.1 객체 리터럴 → Object.prototype
19.6.2 new Object() → 마찬가지로 Object.prototype 계열
19.6.3 new F() → F.prototype
function F() {}
const a = {};
const b = new Object();
const c = new F();
console.log(Object.getPrototypeOf(a) === Object.prototype);
console.log(Object.getPrototypeOf(c) === F.prototype);
WHY: “객체니까 다 같다”가 아니다. instanceof·메서드 공유가 이 결정에 달렸다.
19.7 프로토타입 체인
프로퍼티를 찾으면 자기 → 프로토타입 → … → Object.prototype → null.
체인을 따라가며 찾는다. 이게 상속의 실체.
console.log(dog.toString); // Object.prototype.toString을 위임
console.log(Object.getPrototypeOf(Object.prototype)); // null — 끝
19.8 오버라이딩과 프로퍼티 섀도잉
인스턴스에 같은 키를 쓰면 자기 것이 체인을 가린다(섀도잉). 부모 메서드는 그대로 있다.
dog.hello = function () { return 'woof ' + this.name; };
console.log(dog.hello()); // 인스턴스 쪽
delete dog.hello;
console.log(dog.hello()); // 다시 프로토타입
부모 것을 호출하려면 Animal.prototype.hello.call(this) 또는 class의 super.
19.9 프로토타입의 교체
19.9.1 생성자 쪽 교체
F.prototype = { … }로 갈아끼우면 그 이후 new F()만 새 프로토를 본다. 이미 만든 인스턴스는 옛 링크.
function Foo() {}
const old = new Foo();
Foo.prototype = { x: 1 };
const neu = new Foo();
console.log(Object.getPrototypeOf(old) === Object.getPrototypeOf(neu)); // false
// constructor 복구를 잊기 쉬움
Foo.prototype.constructor = Foo;
19.9.2 인스턴스 쪽 교체
Object.setPrototypeOf(obj, proto) 또는 리터럴 __proto__. 성능·가독성 이유로 남용 금지. 설계 단계에선 Object.create.
19.10 instanceof
obj instanceof Ctor ≈ 체인을 따라가며 Ctor.prototype이 나오는지.
Ctor 자체를 보는 게 아니라 prototype 객체가 체인에 있는지가 기준.
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
교체로 prototype을 바꾸면 instanceof 결과도 바뀐다.
19.11 직접 상속
19.11.1 Object.create
명시적으로 위임 대상을 고르는 공장. new 없이 체인만 잇는다.
const proto = { kind: 'base', hello() { return this.kind; } };
const child = Object.create(proto);
child.kind = 'child';
console.log(child.hello()); // child — this는 child, 메서드는 proto
const dict = Object.create(null); // 순수 사전 — toString 없음
19.11.2 리터럴 __proto__
{ __proto__: proto, … } 문법. 동작은 create와 비슷하나, 스타일·린트상 create 선호.
19.12 정적 프로퍼티/메서드
생성자 함수(또는 class) 자신에 붙는 것. 인스턴스 체인으로는 안 보인다.
function User(name) { this.name = name; }
User.createGuest = function () { return new User('guest'); };
const g = User.createGuest();
// g.createGuest — undefined
Object.keys / Array.isArray / Promise.resolve가 이 계열.
19.13 프로퍼티 존재 확인
19.13.1 in 연산자
체인까지 본다. toString in {} → true.
19.13.2 hasOwnProperty / Object.hasOwn
자기 것만. 프로토타입 키와 충돌하는 사전에는 Object.create(null) 또는 Object.hasOwn.
const person = { name: 'Lee' };
console.log('name' in person); // true
console.log('toString' in person); // true — 체인
console.log(Object.hasOwn(person, 'toString')); // false
19.14 프로퍼티 열거
19.14.1 for…in
자기 + 체인 enumerable. 순서는 정수 인덱스 등 규칙 있음. 프로토타입 오염에 민감.
19.14.2 Object.keys / values / entries
자기 자신의 enumerable 문자열 키만. 실무 기본.
for (const k in person) {
if (!Object.hasOwn(person, k)) continue; // 옛 패턴
}
console.log(Object.keys(person)); // ['name']
생성자·prototype·인스턴스 삼각형 (복기)
function Animal(name) { this.name = name; }
Animal.prototype.hello = function () { return this.name; };
const dog = new Animal('nana');
// dog ──[[Prototype]]──▶ Animal.prototype ──▶ Object.prototype ──▶ null
// Animal.prototype.constructor === Animal
메서드는 프로토타입, 상태는 인스턴스. 17에서 공장을 만들고 여기서 체인을 탄다.
예제 — 왜 이렇게 쳐 봤냐
// 위임으로 타입 확장
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () { return 'wal'; };
const d = new Dog('coco');
console.log(d.hello(), d.bark(), d instanceof Dog, d instanceof Animal);
WHY: create로 체인만 잇고 constructor를 복구한다. class extends가 이 보일러플레이트를 줄인다(25).
// 사전 객체
const cache = Object.create(null);
cache['toString'] = 1; // Object.prototype과 충돌 없음
console.log(Object.keys(cache));
헷갈리던 장면 / 함정
- class만 알고 prototype을 “옛것”으로 취급 → 스택·라이브러리·트랜스파일 결과에 그대로 남음
__proto__로 상속 “설정” 남용- prototype 교체 후 기존 인스턴스·instanceof 혼란
invshasOwn혼동- for…in으로 객체 복사 → 프로토 키까지 복사
- 인스턴스에 메서드 정의 → 공유 실패(17)
표
질문 → 자답
Q. 복사가 아니라 위임이면 부모가 바뀌면? A. 프로토타입 객체를 뮤테이션하면 위임 중인 인스턴스 조회 결과가 바뀐다. 공유의 양날.
Q. null 프로토타입은 언제?
A. 키 저장소·맵 대용. toString 등 오염 방지.
Q. setPrototypeOf 성능? A. 엔진 최적화에 불리할 수 있다. 생성 시 create/new/class로 고정.
웹 보강 메모
MDN prototype chain 다이어그램과 Object.hasOwn(ES2022)를 확인했다. 코드베이스에서 for…in·__proto__·instanceof 오용을 검색해 keys/hasOwn으로 옮길 후보를 표시했다.
실무로 바로
- 도메인 타입: class 또는 생성자+prototype
- 사전:
Object.create(null)또는 Map - 열거:
Object.keys/Object.hasOwn - 상속:
extends우선, 레거시는 create+constructor 복구 - 디버그:
Object.getPrototypeOf반복
여기서 잠깐 💁♂️
25장 class는 이 체인을 설탕으로 감싼다. 체인을 모르면 extends·super가 마법으로 남는다.