Ray Book
프로토타입과 상속

프로토타입이란 무엇인가

JavaScript 객체의 근간인 프로토타입, [[Prototype]], __proto__, Object.getPrototypeOf의 관계를 시각화합니다

javascriptprototypeobjectinheritance

모든 객체는 연결되어 있다

JavaScript에서 객체를 하나 만들어봅시다:

const obj = { a: 1 };
obj.toString(); // "[object Object]", 어디서 왔을까?

obj에는 toString이 없습니다. 그런데 호출이 됩니다. 이것이 프로토타입 (prototype) 의 힘입니다.

모든 JavaScript 객체는 다른 객체와 숨겨진 링크로 연결되어 있습니다. 이 링크를 [[Prototype]] 이라는 내부 슬롯이 관리합니다.

[[Prototype]] 내부 슬롯

const animal = {
  eats: true,
  walk() { return "walking"; }
};

const rabbit = {
  jumps: true,
  __proto__: animal  // rabbit의 [[Prototype]]을 animal로 설정
};

rabbit의 [[Prototype]]이 animal을 가리킵니다. rabbit에서 속성을 찾을 때 없으면, 엔진은 [[Prototype]]을 따라 animal에서 찾습니다.

코드
1const animal = {
2 eats: true,
3 walk() { return "walking"; }
4};
5 
6const rabbit = {
7 jumps: true,
8 __proto__: animal
9};
10 
11rabbit.jumps; // true (자신의 속성)
12rabbit.eats; // true (animal에서 상속)
13rabbit.walk(); // "walking" (animal에서 상속)
프로토타입 체인
animal
eatstrue
walkfunction
animal 객체를 생성합니다. eats와 walk을 자체 속성으로 가집니다.

접근 방법

[[Prototype]]은 내부 슬롯이라 직접 접근할 수 없습니다. 세 가지 방법으로 접근합니다.

__proto__ (레거시)

const rabbit = {};
rabbit.__proto__ = animal;

console.log(rabbit.__proto__ === animal); // true

__proto__Object.prototype의 접근자 프로퍼티입니다. getter/setter로 [[Prototype]]을 읽고 씁니다. 레거시 기능이지만, ECMAScript 명세의 Annex B에 공식 포함되어 있습니다. 표준 대안인 Object.getPrototypeOf를 사용하는 것이 권장됩니다.

Object.getPrototypeOf / Object.setPrototypeOf (표준)

const proto = Object.getPrototypeOf(rabbit);
console.log(proto === animal); // true

Object.setPrototypeOf(rabbit, anotherAnimal);

표준 방법입니다. 코드에서는 이것을 사용하세요.

Object.create (생성 시 설정)

const rabbit = Object.create(animal);
rabbit.jumps = true;

// 위는 아래와 동일합니다
const rabbit = { jumps: true, __proto__: animal };

Object.create(proto)는 [[Prototype]]이 proto인 새 객체를 만듭니다.

읽기와 쓰기의 차이

프로토타입은 읽기 전용으로 공유됩니다. 속성을 읽을 때는 체인을 따라 올라가지만, 쓸 때 는 일반적으로 객체 자신에 씁니다. 단, 프로토타입에 같은 이름의 writable: false 프로퍼티나 setter가 있으면 예외입니다 (다음 글에서 자세히 다룹니다).

const animal = { eats: true };
const rabbit = { __proto__: animal };

// 읽기, 체인을 올라감
console.log(rabbit.eats); // true (animal에서 읽음)

// 쓰기, rabbit에 직접 추가
rabbit.eats = false;

console.log(rabbit.eats);  // false (rabbit 자체)
console.log(animal.eats);  // true  (animal은 변하지 않음)

이것을 프로퍼티 섀도잉 (property shadowing) 이라고 합니다. rabbit.eatsanimal.eats를 "가립니다".

hasOwnProperty

속성이 객체 자체에 있는지, 프로토타입에서 상속된 것인지 구분하려면:

const animal = { eats: true };
const rabbit = { jumps: true, __proto__: animal };

rabbit.hasOwnProperty("jumps"); // true , 자체 속성
rabbit.hasOwnProperty("eats");  // false, 상속된 속성

for...in은 상속된 속성까지 순회하지만, Object.keys()는 자체 속성만 반환합니다.

for (const key in rabbit) {
  console.log(key);
  // "jumps" (자체)
  // "eats" (상속)
}

Object.keys(rabbit);
// ["jumps"] (자체만)

다음 단계

프로토타입의 기본 개념을 이해했습니다. 다음 글에서는 프로토타입이 체인으로 연결되어 있을 때 속성 탐색이 어떻게 동작하는지, 프로토타입 체인 을 살펴보겠습니다.