2019. 07. 21 수정
1. Prototype이란?
JS의 모든 객체는 부모를 갖고 있고, 부모와 연결되어 있습니다.
이로 인해 객체지향 프로그래밍의 상속 개념과 같이 부모의 프로퍼티, 메서드를 물려받아 사용할 수 있습니다.
이 때 부모 객체를 가르켜 Prototype 객체 또는 prototype이라고 합니다.
Prototype을 사용하는 이유는
- 생성자 함수로 생성된 객체 모두에 프로퍼티, 메서드를 공유하기 위해서입니다.
- 상속을 구현할 수 있습니다.
2. __proto__ 와 prototype 프로퍼티
__proto__와 prototype은 프로퍼티이며, 이 두 프로퍼티가 가르키는 객체를 prototype 객체라고 합니다.
__proto__는 사실 크롬 브라우저에서 사용하는 프로퍼티명이고, ECMA 명세서에는 [[Prototype]]이라는 이름으로 사용합니다.
그러나 이 글에서는 __proto__라는 이름으로 사용하도록 하겠습니다.
__proto__와 prototype에 대한 관계는 다음과 같습니다.
- __proto__
- 모든 객체가 갖고 있는 프로퍼티
- 부모의 prototype 프로퍼티에 대한 정보를 의미 ( prototype link )
- prototype
- 함수만 갖고 있는 프로퍼티 ( 함수도 객체이므로 __proto__를 갖고 있음 )
- 자신의 prototype 객체이며 자식 객체는 이를 참조함
예제
var foo = {
name: "victolee"
}
function goo(){} // 생성자 함수
console.dir(foo)
console.dir(goo)
- 함수 goo에는 prototype 프로퍼티가 존재하지만, 리터럴 객체 foo에는 존재하지 않습니다.
- 즉, 함수에만 prototype 프로퍼티가 존재함을 확인할 수 있습니다.
- 또한 foo 객체의 __proto__는 Object 객체이며, goo 함수의 __proto__는 Function 객체입니다.
- 사실 모든 함수는 Function 객체를 물려받은 것이고, 모든 객체는 Object 객체를 물려받은 것입니다.
- 즉, goo 함수는 Object -> Function -> goo 와 같이 prototype 객체에 의해 상속이 된 것입니다 !
다시 말하면, 생성자 함수 goo의 __proto__는 Function.prototype 객체를 가리키며, Function 생성자 함수의 __proto__는 Object.prototype을 가르킵니다.
따라서 Function 객체의 __proto__ 값은 Object 객체이기 때문에 일반 객체처럼 함수에 속성을 정의할 수 있는 것입니다.
이번에는 prototype 프로퍼티에 대해 알아보도록 하겠습니다.
우선 생성자 함수로 클래스를 정의하는 방법입니다.
let foo = function(){
this.name = "victolee",
this.email = "foo@example.com"
}
let fooObj = new foo();
이 코드를 다음과 같이 바꿔 쓸 수 있습니다.
let foo = function(){}
foo.prototype.name = "victolee";
foo.prototype.email = "foo@example.com"
let fooObj = new foo();
이와 같이 prototype 프로퍼티를 통해 생성자 함수로 생성된 객체 모두에 프로퍼티, 메서드를 공유할 수 있습니다.
또 다른 차이점
- __proto__은 부모 property를 사용할 수 있으므로, __proto__에 있는 프로퍼티를 직접 사용할 수 있습니다.
- 그러나 prototype에 연결된 메서드는 object.prototype.프로퍼티로 접근해야 합니다.
3. Prototype chain
객체의 속성에 접근하려면 객체.프로퍼티이름으로 접근할 수 있었습니다.
그런데 객체에 해당 property가 없을 경우에는 부모 객체의 property를 참조하게 됩니다.
부모 객체의 property를 참조한다는 것은 엄밀하게 말하면,
현재 객체의 __proto__ 프로퍼티를 참조해서 해당 프로퍼티가 있는지 체크하고, 그래도 없으면 부모의 __proto__ 프로퍼티를 참조해서 해당 프로퍼티를 체크하는 것을 말합니다.
이것을 Prototype chain이라 합니다.
prototype chain의 종점은 Object.prototype입니다.
만약 해당 프로퍼티가 Object.prototype에도 없다면 오류가 발생합니다.
4. 상속
JS에서 상속을 구현하기 위해서는 prototype 객체를 이용하며, __proto__ 프로퍼티를 이용하면 상속을 구현할 수 있습니다.
다음 예제는 상속 및 prototype chain을 이용하는 예제입니다.
예제
let foo = function(){
this.name = "victolee"
}
let goo = function(){}
goo.prototype = new foo();
goo.prototype.constructor = goo;
let gooObj = new goo();
console.log(gooObj.name)
console.dir(gooObj)
- foo 함수는 name 프로퍼티를 가진 객체를 반환합니다.
- goo는 빈 객체이기 때문에 name이라는 프로퍼티가 없습니다.
- 그런데 goo.prototype = new foo(); 를 작성하면 상속이 됩니다.
- 따라서 foo의 프로퍼티를 사용할 수 있고, 따라서 gooObj.name의 값은 victolee가 됩니다.
- goo의 프로퍼티에 name이 없기 때문에 __proto__가 참조하는 prototype 객체로 찾아가 name 프로퍼티를 읽어온 것입니다.
- 즉, prototype chain이 적용되었습니다.
constructor 프로퍼티는 뒤에서 말씀드리겠지만 생성자를 의미합니다.
foo를 상속받은 goo는 constructor 프로퍼티가 없기 때문에 자신이 생성자라는 것을 작성해야 합니다.
그렇지 않으면 new goo() 로 객체를 생성해도, 생성자가 foo로 되어있습니다.
5. constructor 프로퍼티
prototype 객체(__proto__프로퍼티 , prototype 객체)는 constructor 프로퍼티를 갖습니다.
정말로 그러한지 처음에 봤던 예제를 다시 보도록 하겠습니다.
var foo = {
name: "victolee"
}
var goo = function(){}
console.dir(foo)
console.dir(goo)
constructor 프로퍼티는 객체를 생성하는 생성자 함수 객체를 의미합니다.
예제
var foo = function(){ }
var goo = new foo();
console.log(goo);
- 생성자 함수인 foo를 호출하여 변수 goo에 할당했습니다.
- goo의 prototype 객체의 constructor 프로퍼티를 보니 foo이름을 가진 함수를 값으로 갖고 있습니다.
- 즉, prototype 객체의 constructor 프로퍼티를 통해 어떤 함수가 호출했는지 알 수 있습니다.
6. 정리
이상으로 prototype 객체에 대해 알아보았습니다.
기억해야 할 점은,
- __proto__ 프로퍼티와 prototype 프로퍼티의 차이점
- prototype chain
- 상속을 어떻게 하는지
- constructor 프로퍼티