프로토타입 — 없으면 위에 물어보는 상속
내가 만들지도 않은 toString·map 같은 메서드를 쓸 수 있는 이유. 객체가 다른 객체를 참조해 속성을 물려받는 구조다.
Seobway · · 8분
만든 적 없는 메서드를 왜 쓸 수 있는가
빈 객체를 만들었을 뿐인데 메서드가 딸려 온다.
const obj = {};
obj.toString(); // "[object Object]" ← 만든 적 없다
const arr = [1, 2, 3];
arr.map((n) => n * 2); // 이것도 만든 적 없다
obj 에는 아무것도 없다. 그런데 toString 을 찾아 준다. 자기한테 없으면 위에 물어보기
때문이다.
프로토타입은 무엇인가
프로토타입은 객체가 참조하는 다른 객체이고, 자기한테 없는 속성을 여기서 찾는다.
모든 객체는 내부에 [[Prototype]] 이라는 숨은 링크를 하나 갖고 있다.
속성을 읽을 때 자바스크립트는 이렇게 움직인다.
- 객체 자신에게 있나? 있으면 끝.
- 없으면
[[Prototype]]을 따라 올라가 거기서 찾는다. - 거기도 없으면 또 올라간다.
- 끝(
null)까지 갔는데 없으면undefined.
이 사슬을 프로토타입 체인이라고 한다.
인터랙티브 다이어그램: arr.map 을 찾아 위로 올라간다 (단계별로 재생할 수 있다)
- arr.map 을 읽는다. arr 자신에는 0,1,2 와 length 뿐이다.
- 없으니 [[Prototype]] 을 따라 Array.prototype 으로 올라간다.
- Array.prototype 에 map 이 있다. 여기서 멈춘다.
- 만약 toString 이었다면 여기도 없어 한 칸 더 올라간다.
- Object.prototype 에서 찾는다.
- 여기에도 없었다면 null 에 닿고 undefined 를 돌려준다.
어떻게 연결하는가
가장 직접적이다. "이 객체를 프로토타입으로 삼는 객체를 만들어라".
const animal = {
speak() { return `${this.name} 이(가) 소리를 낸다`; },
};
const dog = Object.create(animal);
dog.name = "바둑이";
dog.speak(); // "바둑이 이(가) 소리를 낸다"
dog 에는 name 뿐이지만 speak 는 animal 에서 찾아 쓴다.
new 를 쓰면 만들어진 객체의 [[Prototype]] 이 함수.prototype 으로 연결된다.
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
return `${this.name} 이(가) 소리를 낸다`;
};
const dog = new Animal("바둑이");
Object.getPrototypeOf(dog) === Animal.prototype; // true
speak 를 this 마다 만들지 않고 한 벌만 두는 것이 요점이다.
문법만 다르고 하는 일은 위와 같다. 자바스크립트에는 클래스 기반 상속이 따로 없다.
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} 이(가) 소리를 낸다`; }
}
const dog = new Animal("바둑이");
Object.getPrototypeOf(dog) === Animal.prototype; // true
메서드는 인스턴스가 아니라 Animal.prototype 에 올라간다.
직접 확인
체인을 끝까지 따라가 보면 구조가 한눈에 보인다.
function walk(label, value) {
const chain = [];
let cur = Object.getPrototypeOf(value);
while (cur) {
chain.push(cur.constructor ? cur.constructor.name : "(익명)");
cur = Object.getPrototypeOf(cur);
}
console.log(label, "→", chain.join(" → "), "→ null");
}
walk("[1,2,3] ", [1, 2, 3]);
walk("{} ", {});
walk("'문자열' ", "문자열");
walk("function ", function () {});
// 자기 것인지 물려받은 것인지 구분
const dog = Object.create({ speak() {} });
dog.name = "바둑이";
console.log("name 은 자기 것?", Object.hasOwn(dog, "name"));
console.log("speak 은 자기 것?", Object.hasOwn(dog, "speak"));
console.log("speak 을 쓸 수는 있나?", typeof dog.speak);
speak 은 자기 것이 아닌데도 쓸 수 있다. 이것이 프로토타입 상속이다.
흔한 실수
한 줄 정리
프로토타입은 객체가 참조하는 다른 객체이고, 자기한테 없는 속성은 이 링크를 따라
위로 올라가며 찾는다. class 도 이 구조 위의 문법이고, 메서드를 한 벌만 두어
인스턴스가 공유하게 하는 것이 목적이다.
-
obj.toString()이 동작하는 이유를 체인으로 설명할 수 있다 -
Foo.prototype과obj.__proto__의 차이를 말할 수 있다 - 프로토타입에 배열을 두면 안 되는 이유를 안다
참고
- [1] Inheritance and the prototype chain — MDN Web Docs
- [2] Object.getPrototypeOf() — MDN Web Docs
- [3] Objects — ECMAScript 사양
관련 글
- javascript
- 프로토타입
- 상속
- 객체