자바스크립트는 프로토타입 기반 언어라고 한다. 객체를 다룰 때 프로토타입을 사용하기 때문이다. 최근 추가된 class 문법 또한 다른 언어와 달리 프로토타입 기반이다. 그러므로, 자바스크립트를 다룰려면 프로토타입을 이해하는 것이 중요하다.
자바스크립트에서 생성된 모든 객체는
[[Prototype]]
이라는 숨겨진 내부 속성을 갖고 있다. 이 속성은 자신의 프로토타입 객체를 가리킨다. 프로토타입 객체에 접근하는 데[[Prototype]]
속성은 사용할 수 없으므로, 그 존재를 알아두기만 하고 이 이후로 더 이상 언급하지 않겠다.
__proto__
개념을 언급할 것인데, 이는 표준이 아니라서(참고1, 참고2)Object.getPrototypeOf()
와Object.setPrototypeOf()
로 대체된다. 하지만,__proto__
는 많은 브라우저에서 구현되어 사실상 표준이 되었기 때문에 이하 내용에선__proto__
를 사용한다.
자바스크립트에서 모든 객체는 Object
라는 생성자 함수로부터 시작된다.
console.log(typeof Object); // function
모든 함수는 prototype
이라는 속성을 가지고 있는데, 이 속성은 함수가 정의될 때 같이 생성되는 프로토타입 객체를 참조한다. 프로토타입 객체는 constructor
라는 속성을 가지고 있고, 이 속성은 생성자 함수를 참조하고 있다.
다시 말해, prototype
은 프로토타입(prototype) 객체를 가리키고, constructor
는 생성자(constructor) 함수를 가리킨다. 즉, 서로를 상호 참조하고 있다.
console.log(Object.prototype); // {constructor: f}
console.log(Object.prototype.constructor === Object); // true
모든 함수라고 언급했지만, 프로토타입 객체는 생성자 함수일 때 의미가 있는 객체이다. 즉, new
연산자로 새로운 객체가 생성될 때, 그 생성된 객체도 프로토타입 객체를 가리킨다. 우리가 흔히 사용하는 객체도 Object
의 프로토타입 객체를 참조하고 있다. 우리는 객체를 생성할 때 객체 리터럴을 사용한다. 이는, 주석의 방법과 동일하다.
let obj = {}; // let obj == new Object();
생성자 함수는 prototype
속성이 프로토타입 객체를 참조하고 있다고 했다. 그와 달리,new
연산자로 생성된 객체는 __proto__
속성이 프로토타입 객체를 참조하고 있다.
let obj = {}; // let obj == new Object();
console.log(obj.__proto__); // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Object.prototype === obj.__proto__); // true
console.log(Object === obj.__proto__.constructor); // true
생성자 함수의 프로토타입 객체와 생성된 객체의 __proto__
속성이 참조하는 프로토타입 객체가 동일한 것을 확인할 수 있다.
같은 생성자 함수로부터 생성된 객체는 모두 같은 프로토타입 객체를 참조하고 있다.
let obj1 = {}; // let obj = new Object();
let obj2 = {}; // let obj = new Object();
console.log(obj1.__proto__ === obj2.__proto__); // true
모든 객체가 공유해야할 정보(e.g. 메서드)는 프로토타입 객체에서 관리하는 것이 유리하다고 볼 수 있다.
우리가 생성자 함수에 메서드를 추가할 때 아래와 같은 방법으로 한다.
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() { console.log(`Hi! I'm ${this.name}.`); };
let park = new Person('Park');
park.sayHi(); // Hi! I'm Park.
console.log(park);
// Person {name: "Park"}
// - name: "Park"
// - __proto__: Object
park
의 내부를 살펴보면 name
속성과 __proto__
속성만 확인할 수 있다. sayHi()
는 어떻게 사용할 수 있을까? 우리가 객체의 속성에 접근할 때, 자바스크립트 내부에선 보이지 않는 동작이 실행된다. 일단, 해당 객체에서 속성을 찾는다. 만약 속성이 없다면, 해당 객체의 프로토타입 객체를 확인한다. 또 속성이 없다면 해당 프로토타입 객체의 프로토타입 객체에서 확인한다. 이를 찾을 때까지 반복한다. 이렇게 연결되어 있는 모습을 프로토타입 체인이라고 하고, 프로토타입 체인을 타고 올라가며 속성을 찾는 동작 방식을 프로토타입 상속이라고 한다.
위에서 new
로 생성된 객체는 생성자 함수의 프로토타입 객체를 참조하고 있다고 했다. 프로토타입 상속을 적용하면 굳이 우리가 직접 접근하지 않더라도 알아서 찾아낸다.
let obj = {}; // let obj = new Object();
console.log(Object === obj.__proto__.constructor); // true
console.log(Object === obj.constructor); // true
[참고]
모던 JavaScript 튜토리얼
생활코딩 - prototype vs proto
생활코딩 - __proto__
생활코딩 - 상속
ZeroCho Blog - 생성자와 프로토타입
ZeroCho Blog - 객체 상속
stack overflow - __proto__, when will it be gone? Alternatives?
stack overflow - What is the end of prototype chain in javascript — null or Object.prototype?
상속과 프로토타입 - MDN Document