[javascript] 프로토타입
* 코어 자바스크립트 서적을 공부하며 기록하는 글입니다.
프로토타입
constructor, prototype, instance
var instance =new Constructor();
- 어떤 생성자 함수Constructor 를 new 연산자와 함께 호출하면 Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스instance 가 생성된다. 이때 instance에는 __proto__라는 프로퍼티가 자동으로 부여(생략 가능)되는데, 이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조한다.
- prototype은 객체임 (proto 또한 객체)
- prototype 객체 내부에는 인스턴스가 사용할 메서드를 저장함
- 인스턴스에서도 숨겨진 프로퍼티인 __proto__를 통해 메서드들에 접근할 수 있음
- 실무에서는 가급적 __proto__를 사용하지 않고, getPrototypeOf() / Object.create() 등을 이용하는 것을 권장
① constructor, prototype, instance
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 프로퍼티를 참조해 둘은 같은 객체를 바라보기 때문
② consturctor 프로퍼티
생성자 함수의 프로퍼티인 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)); //지금
- undefined → this가 prototype 객체를 가리키는데 prototype에는 name 프로퍼티가 없기 때문
- prototype에 name 프로퍼티를 추가해 name 출력됨
- this가 prototype을 바라보는 것이 아니라 인스턴스를 바라보도록 바꿔줄수도 있음
- call/apply 메소드
② 프로토타입 체인
__proto__는 생략 가능하기 때문에 배열이 array.prototype 내부의 메서드를 마치 자신의 것처럼 실행할 수 있다. 마찬가지로 object.prototype 내부의 메서드도 자신의 것처럼 실행할 수 있다. 생략 가능한 __proto__를 한 번 더 따라가면 object.prototype을 참조할 수 있기 때문
프로토타입 체인 prototype chain : 어떤 데이터의 proto 프로퍼티 내부에서 다시 proto 프로퍼티가 연쇄적으로 이어진 것
프로토타입 체이닝 prototype chianing : 프로토타입 체인을 따라가며 검색하는 것 (메서드 오버라이드와 동일한 맥락)
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 변수는 배열이므로 arr.__proto__는 Array.prototype을 참조하고, Array.prototype은 객체이므로 Array.prototype.__proto__는 Object.prototype을 참조합니다.
배열뿐만 아니라 자바스크립트의 모든 데이터가 프로토타입 체인 구조를 가짐
객체 전용 메서드의 예외사항
어떤 생성자 함수이든 prototype은 반드시 객체이기 때문에 object.prototype이 언제나 프로토타입 체인의 최상단에 존재하게 된다. 따라서 객체에서만 사용할 메서드는 다른 여느 데이터 타입처럼 프로토타입 객체안에 정의할 수 없다. 객체에서만 사용할 메서드를 object.prototype 내부에 정의한다면 다른 데이터 타입도 해당 메서드를 사용할 수 있게 되기 때문.
다중 프로토타입 체인
다중 프로토타입 체인을 연결하는 방법은 __proto__가 가리키는 대상, 즉 생성자 함수의 prototype이 연결하고자 하는 상위 생성자 함수의 인스턴스를 바라보게끔 해주면 된다.
var Grade = function () {
var args = Array.prototype.slice.call(arguments);
for (var i = 0'; i < args.length; t++) {
this[i] = args[i];
}
this.length = args.length;
};
var g = new Grade(100, 80);
변수 g는 Grade의 인스턴스를 바라보는데 Grade의 인스턴스는 배열의 형태를 지니지만, 배열의 메서드를 사용할 수 없는 유사배열객체이다.
인스턴스에서 배열 메서드를 직접 쓸 수 있게끔 하려면 g.proto, 즉 Grade.prototype이 배열의 인스턴스를 바라보게 하면 된다.
Grade.prototype = [];
g 인스턴스 입장에서는 프로토타입 체인에 따라 g 객체 자신이 지니는 멤버, Grade의 prototype에 있는 멤버, Array.prototype에 있는 멤버, Object.prototype에 있는 멤버에까지 접근할 수 있게 된다.