자바스크립트는 프로토타입 기반 언어입니다. 클래스 기반의 언어에서는 상속을 사용하지만, 프로토타입 기반 언어에서는 어떤 객체를 원형으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻습니다. (유명한 대부분의 프로그래밍 언어의 상당수가 클래스 기반입니다.)
var instance = new Constructor();
__proto__
라는 프로퍼티가 자동으로 부여됩니다.prototype
이라는 프로퍼티를 참조합니다.여기서 새롭게 등장한 prototype
이라는 프로퍼티와 __proto__
라는 프로퍼티의 관계가 프로토타입 개념의 핵심입니다.
prototype
은 객체입니다. 이를 참조하는 __proto__
역시 당연히 객체입니다.
prototype
객체 내부에는 인스턴스가 사용할 메소드를 저장합니다. 그러면 인스턴스에서도 숨겨진 프로퍼티인 __proto__
를 통해 이 메소드들에 접근할 수 있게 됩니다.
예를 들어 Person이라는 생성자 함수의 prototype에 getName이라는 메소드를 지정했다 보겠습니다.
var Person = function (name) {
this._name = name;
}
Person.prototype.getName = function() {
return this._name;
}
var suzi = new Person('Suzi');
Person.prototype === suzi.__proto__ // true
이제 Person의 인스턴스는 __proto__
프로퍼티를 통해 getName을 호출할 수 있습니다. 왜냐하면 인스턴스의 __proto__
가 Constructor의 prototype 프로퍼티를 참조해서 둘은 같은 객체를 바라보기 때문입니다.
생성자 함수의 프로퍼티인 prototype 객체 내부에는 constructor라는 프로퍼티도 존재합니다. 인스턴스의 __proto__
객체 내부에도 마찬가지입니다. 이 프로퍼티는 단어 그대로 원래 생성자 함수(자기 자신)를 참조합니다. 이를 가지고 있는 이유는 인스턴스로부터 그 원형이 무엇인지를 알 수 있는 수단이기 떄문입니다.
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]
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 메소드가 호출됐습니다.__proto__
에 있는 메소드는 자신에게 있는 메소드보다 검색 순서에서 밀려 호출되지 않은 것입니다.이러한 현상을 메소드 오버라이드라고 합니다. 메소드 위에 메소드를 덮어씌웠다는 표현입니다. 즉, 원본을 다른 대상으로 교체하는 것이 아니라 원본이 그대로 있는 상태에서 다른 대상을 그 위에 얹는 이미지를 떠올리면 되겠습니다.
자바스크립트 엔진이 getName 메소드를 찾는 방식은 가장 가까운 대상인 자신의 프로퍼티를 검색하고 없을 경우 그 다음으로 가까운 대상을 검색하는 순서로 진행되기 때문이죠.
그렇다면 메소드 오버라이딩이 이루어져 있는 상황에서 prototype에 있는 메소드에 접근하려면 어떻게 해야할까요?
//(1)
console.log(iu.__proto__.getName()) //undefined
//(2)
Person.prototype.name = '이지금';
console.log(iu.__proto__.getName()); //이지금
//(3)
console.log(iu.__proto__.getName.call(iu)); //지금
어떤 데이터의 __proto__
프로퍼티 내부에 다시 __proto__
프로퍼티가 연쇄적으로 이어진 것을 프로토타입 체인이라 하고, 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝이라고 합니다.
이는 메소드 오버라이드와 동일한 맥락입니다. 어떤 메소드를 호출하면 자바스크립트 엔진은 자신의 프로퍼티를 검색해서 원하는 메소드가 있으면 그 메소드를 실행하고 없는 경우 __proto__
를 검색해서 있으면 그 메소드를 실행하는 방식으로 진행합니다.
var arr = [1,2];
Array.prototype.toString.call(arr); //1,2
Object.prototype.toString.call(arr); //[object Array]
arr.toString(); //1,2
arr.toString = function () {
return this.join('_');
};
arr.toString(); //1_2
arr.__proto__
는 Array.prototype을 참조하고, Array.prototype은 객체이므로 Array.prototype.__proto__
는 Ojbect.prototype을 참조합니다.위의 글은 온전히 학습 목적을 위해 작성한 글입니다. 위 내용의 모든 지적 재산권,저작권은 코어 자바스크립트 저자에게 있으며, 무단 전재 및 재배포를 금합니다.