자바스크립트는 프로토타입 기반 언어이다.
클래스 기반 언어에서는 상속
을 사용하지만 프로토타입 기반 언어에서는 어떤 객체를 원형(프로토타입)
으로 삼고 이를 복제(참조)하여 상속과 비슷한 효과를 얻는다.
위 그림은 아래 코드를 추상화한 것이다.
var instance=new Constructor();
왼쪽(실선)의 꼭짓점에는 Constructor(생성자 함수)
를 오른쪽 꼭지점에는 Constructor.prototype
이라는 프로퍼티를 위치시켰다.
왼쪽 꼭짓점으로부터 아래를 향한 화살표 중간에 new
가 있고, 화살표의 종점에는 instance
가 있다. 오른쪽 꼭짓점으로부터 대각선 아래로 향하는 화살표의 종점에는 instance._proto_
라는 프로퍼티를 위치시켰다.
- 어떤 생성자 함수Constructor를 new 연산자와 함께 호출하면
- Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스가 생성된다
- 이때 instance에는
__ Proto__
라는 프로퍼티가 자동으로 부여되는데,- 이 프로퍼티는 Constructor의 Prototype이라는 프로퍼티를 참조한다.
👉 프로토타입의 핵심 개념 : Prototype
프로퍼티, __ Proto__
프로퍼티
둘은 객체를 의미한다. prototype 객체 내부에는 인스턴스가 사용할 메서드를 저장한다. 그러면 인스턴스에서도 숨겨진 프로퍼티인 __ Proto__
를 통해 이 메서드들에 접근할 수 있게 된다.
예를들어, `Person이라는 생성자 함수의 prototype에 getName이라는 메서드를 지정했다.
// Person.prototype
var Person =function(name){
this._name=name;
};
Person.prototype.getName=function(){
return this._name;
};
이제 Person의 인스턴스는 __Proto__
프로퍼티를 통해 getName을 호출할 수 있다.
var suzi=new Person('Suzi')
suzi.__Proto__.getName();//undefined
Person.prototype ===suzi.__proto__//true
왜냐하면 instance의 __Proto__
가 Constructor의 prototype프로퍼티를 참조하므로 결국 둘은 같은 객체를 바라보기 때문이다.
어떤 함수를 메소드로서 호출할 때는 메서드명 바로 앞의 객체가 곧 this
가 된다.
즉,thomas.__proto__.getName()
에서 getName 함수 내부에서의 this는 thomas가 아니라 thomas.__proto__
라는 객체가 되는 것이다.
var suzi=new Person('Suzi',28);
suzi.getName();//Suzi
var iu=new Person('Jieun',28);
iu.getName();//Jieun
__proto__
는 생략가능한 프로퍼티
suzi.__proto__.getName
->suzi(.__proto__).getName
-> suzi.getName
__Proto__
를 생략하지 않으면 this는 suzi.__proto__
를 가리키지만, 이를 생략하면 suzi를 가리킨다.
🙂 다시 프로토타입을 정의해보자면 "new 연산자로 constructor를 호출하면 instance가 만들어지는데, 이 instance의 생략 가능한 프로퍼티인 __proto__
는 constructor의 prototype을 참조한다~!"
Constructor프로퍼티는 단어 그대로 원래의 생성자 함수(자기 자신)를 참조한다. 인스턴스로부터 그 원형이 무엇인지를 알 수 있는 수단이기에 필요한 정보!
생성자는 기본적으로 함수다.
var arr=[1,2];
Array.prototype.constructor===Array //true
arr.__proto__.constructor===Array//true
arr.constructor===Array//true
var arr2=new arr.constructor(3,4);
console.log(arr2);//[3,4]
인스턴스의 __proto__
가 생성자 함수의 prototype프로퍼티를 참조하며 __proto__
가 생략가능하기에 인스턴스에서 직접 constructor에 접근할 수 있는 수단이 생긴 것이다.
var Person=function(name){
this.name=name;
};
var p1=new Person('사람1');
var p1Proto=Object.getPrototypeOf(p1);
var p2= new Person.prototype.constructor('사람2');
var p3= new p1Proto.constuctor('사람3');
var p4= new p1.__proto__.constructor('사람4');
var p5=new p1.constructor('사람5');
[p1,p2,p3,p4,p5].forEach(function(p){
console.log(p,p instanceof Person);
});
p1에서 p5까지는 모두 person의 인스턴스이다.
prototype 객체를 참조하는 __proto__
를 생략하면 인스턴스는 prototype에 정의된 프로퍼티나 메서드를 마치 자신의 것처럼 사용할 수 있다고 했다. 그런데 만약 인스턴스가 동일한 이름의 프로퍼티 또는 메서드를 가지고 있는 상황이라면?
var Person=function(name){
this.name=name;
};
Person.prototype.getName=function(){
return this.name;
};
var iu=new Person('지금');
iu.getName=function(){
return '바로'+this.name;
};
console.log(iu.getName());//바로 지금
iu.__proto__.getName
이 아닌 iu객체에 있는 getName메서드가 호출됐다. 여기서 일어난 현상을 메서드 오버라이드라고 한다. 메서드 위에 메서드를 덮어씌었다는 표현이다.
자바스크립트 엔진이 getName이라는 메서드를 찾는 방식은 가장 가까운 대상인 자신의 프로퍼티를 검색하고, 없으면 그 다음으로 가까운 대상인 __proto__
를 검색하는 순서로 진행된다.