자바스크립트의 비밀스러운 삶: 프로토타입 이해하기
Source: Dev.to
객체는 위임하고, 복사하지 않는다
티모시가 노트북을 들고 도서관에 도착했지만 표정이 어리둥절했다. 그는 JavaScript 상속에 대해 읽고 있었는데, 뭔가 맞지 않았다.
Timothy: “JavaScript가 상속에 프로토타입을 사용한다는 걸 배웠어요. 그런데 이제는 클래스도 있다고 들었어요. 그럼 어느 쪽인가요? JavaScript는 프로토타입을 쓰나요, 아니면 클래스를 쓰나요?”
Margaret: “두 다. 그리고 어느 쪽도 아니야. 그보다 더 미묘해.”
상속이란 무엇인가?
Timothy: “자식 클래스가 부모 클래스를 상속받으면, 부모의 모든 속성과 메서드를 얻게 돼요.”
Margaret: “Python이나 Java 같은 언어에서는 그렇지. 자식 클래스가 부모의 동작을 복사하거나 포함해. 하지만 JavaScript는 위임을 사용해.”
위임 vs. 복사
속성을 복사하는 대신, JavaScript의 객체들은 다른 객체를 가리키며 이렇게 말한다: “내게 원하는 것이 없으면, 저쪽 객체에게 물어봐.”
간단한 예시
const parent = {
greet: function() {
console.log("Hello, " + this.name);
}
};
const child = Object.create(parent);
child.name = "Alice";
child.greet(); // "Hello, Alice"
Object.create(parent)은 숨겨진[[Prototype]]링크가parent를 가리키는 새 객체를 만든다.child.greet()가 호출되면, JavaScript 는 먼저child에서greet를 찾는다. 없으면child의 프로토타입(parent) 에서 찾는다. 찾으면this를child로 바인딩해 실행한다.
같은 프로토타입을 공유하는 여러 객체
const parent = {
greet: function() {
console.log("Hello, " + this.name);
}
};
const alice = Object.create(parent);
alice.name = "Alice";
const bob = Object.create(parent);
bob.name = "Bob";
alice.greet(); // "Hello, Alice"
bob.greet(); // "Hello, Bob"
alice 와 bob 은 동일한 parent 객체에 위임하지만, greet 내부의 this 는 호출한 객체를 가리킨다.
[[Prototype]] vs. .prototype
숨겨진 링크 [[Prototype]]
const obj = Object.create(parent);
// obj.[[Prototype]] points to parent (invisible)
함수에 있는 .prototype 프로퍼티
function User(name) {
this.name = name;
}
// User.prototype is a regular property on the User function
console.log(User.prototype); // { constructor: User }
new 사용하기
function User(name) {
this.name = name;
}
User.prototype.greet = function() {
console.log("Hello, " + this.name);
};
const alice = new User("Alice");
alice.greet(); // "Hello, Alice"
new User("Alice") 가 실행될 때 JavaScript 는:
- 완전히 새로운 객체를 만든다.
- 그 객체의
[[Prototype]]을User.prototype으로 설정한다. User함수를this가 새 객체가 되도록 호출한다.- 새 객체를 반환한다.
링크를 확인할 수 있다:
console.log(Object.getPrototypeOf(alice) === User.prototype); // true
Object.getPrototypeOf() (또는 레거시 __proto__) 는 보이지 않는 프로토타입 체인을 드러낸다.
클래스: 프로토타입 위에 얹은 문법 설탕
앞 예시의 클래스 버전
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log("Hello, " + this.name);
}
}
const alice = new User("Alice");
alice.greet(); // "Hello, Alice"
- 클래스 문법은 내부적으로 동일한 프로토타입 체인을 만든다.
- 클래스 안에 정의된 메서드(
greet) 는User.prototype에 배치된다. new User()는 여전히[[Prototype]]이User.prototype을 가리키는 객체를 만든다.
왜 “문법 설탕”인가?
이 용어는 문법이 다르게 보이고 보통 더 명확하지만, 근본적인 메커니즘—프로토타입 위임—은 변하지 않는다는 뜻이다. 클래스는 extends 와 super() 같은 편리한 키워드를 제공해 프로토타입 체인을 자동으로 처리해 주지만, 새로운 상속 모델을 도입하는 것은 아니다.
요약
- JavaScript 객체는 숨겨진
[[Prototype]]링크를 통해 다른 객체에 위임한다. - 생성자 함수에 있는
.prototype프로퍼티는new로 만든 객체들의 숨겨진 링크를 설정하는 방법이다. Object.getPrototypeOf()(또는__proto__) 로 프로토타입 체인을 검사할 수 있다.- ES6 클래스는 같은 프로토타입 기반 패턴을 더 깔끔하게 작성할 수 있게 해 주는 것이며, 프로토타입을 대체하지 않는다.