[JavaScript] 프로토타입 (Prototype)
1. 프로토타입 (Prototype)
- 자바스크립트는 프로토타입 기반 언어
- 클래스 기반 언어에선 상속을 사용하지만 프로토타입 기반 언어에선느 어떤 객체를 원형(prototype)으로 삼고 이를 복제(참조)함으로서 상속과 비슷한 효과를 나타낸다
1) 프로토타입 개념 이해
(1) 프로토타입 도식화
- 어떤 생성자 함수 (Constructor)를 new 연산자와 함께 호출하면 새로운 인스턴스가 생성
- 이때 instance는
__**proto**__
라는 프로퍼티가 자동으로 부여됌 이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조함
- 메서드 호출을 통한
undefined
는 에러가 발생하지 않았다는 의미 ⇒ 호출 할 수 있는 함수에 해당 - Person.prototype과 dongnyeong.__proto__은 같은 객체를 바라보므로 true
__proto__
객체에name
프로퍼티가 존재할 경우
let Person = function(name) {
this._name = name;
}
Person.prototype.getName = function() {
return this._name;
}
var dongnyeong = new Person('Dongnyeong');
dongnyeong.__proto__.name = 'DONGNYEONG__proto__';
console.log(dongnyeong.__proto__.name); // DONGNYEONG__proto__
__proto__
는 생략 가능한 프로퍼티다
var dongnyeong = new Person('Dongnyeong',28);
console.log( dongnyeong.getName()) // Dongnyeong
var dongnyeong2 = new Person('Dongnyeong2',29);
console.log( dongnyeong2.getName()) // Dongnyeong2
let Person = function(name) {
this._name = name;
}
Person.prototype.getName = function() {
return this._name;
}
var dongnyeong = new Person('Dongnyeong');
console.log(dongnyeong.__proto__.getName()); // undefined
console.log(Person.prototype === dongnyeong.__proto__); // true
간단 요약:
- 자바스크립트는 함수에 자동으로 객체인
prototype
프로퍼티를 생성함 - 해당 함수를 생성자 함수로서 사용할 경우 ,
new
연산자와 함께 함수를 호출할 경우 이로 부터 생성된 인스턴스에는 숨겨진 프로퍼티를 참조함 __proto__
프로퍼티는 생략 가능하도록 구현되있음- 생성자 함수의
prototype
에 어떤 메서드 또는 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티에 접근 가능하다
(2) constructor 프로퍼티
- 생성자 함수의 프로퍼티인 prototype 객체 내부에넌 constructor라는 프로퍼티가 있다
- 생성자 함수(자기 자신)을 참조함
var arr = [1,2];
console.log(Array.prototype.constructor === Array) // true
console.log(arr.__proto__.constructor === Array) // true
console.log(arr.constructor === Array) // true
var arr2 = new arr.constructor(3,4);
console.log(arr2) // [3,4]
- construtor 변경하기
var NewConstructor = function() {
console.log('this is new constructor');
}
var dataTypes = [
1, // Number
'test', // String
true, // Boolean
{}, // Object
[], // Array
function() {}, // Function
/test/, // NewConstructor & false
new Number(), // NewConstructor & false
new String(), // NewConstructor & false
new Boolean(), // NewConstructor & false
new Object(), // NewConstructor & false
new Array(), // NewConstructor & false
new Function(), // NewConstructor & false
new RegExp(), // NewConstructor & false
new Date(), // NewConstructor & false
new Error() // NewConstructor & false
];
dataTypes.forEach(function(d) {
d.constructor = NewConstructor;
console.log(d.constructor.name, d instanceof NewConstructor);
});
- 모든 데이터가 d instanceof NewConstructor 명령에 false를 반환
- 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.constructor('사람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);
});
// Person { _name: '사람1' } true
// Person { _name: '사람2' } true
// Person { _name: '사람3' } true
// Person { _name: '사람4' } true
// Person { _name: '사람5' } true
2) 프로토타입 체인
(1) 메서드 오버라이드
var Person = function(name) {
this._name = name;
}
Person.prototype.getName = function() {
return this._name;
}
var 아이유 = new Person('지금');
아이유.getName = function() {
return '바로 ' + this._name;
}
console.log(아이유.getName()); // 바로 지금
console.log(아이유.__proto__.getName()); // undefined
Person.prototype.name = '이지금'
console.log(아이유.__proto__.name); // 이지금
console.log(아이유.__proto__.getName.call(아이유)); // 지금
아이유.__proto__.getName
이 아닌아이유
객체에 있는getName
메서드가 호출 됨- 메서드 위에 메서드를 덮어 씌워서 메서드 오버라이드
아이유.__proto__.getName()
:undefined
⇒ this가 prototype 객체를 가리키는데 prototype 상 name 프로퍼티가 없어서- this가 prototype을 바라보고있는데 이걸 인스턴스로 바꿈 (call/apply)
(2) 프로토타입 체인
var arr= [1,2];
arr.push(3);
console.log(arr.hasOwnProperty(2)); // true
// arr.push를 실행했을때 __proto__가 실행
// arr.hasOwnProperty(2);를 실행했을떈 __proto__가 2번 실행
__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 변수는 배열이므로 arr._proto__는 Array.prototype을 참조하고, Array.prototype은 객체이므로 Array.prototype.__proto__는 Object.prototype을 참조함