1. 프로토타입의 개념
- 자바스크립트는 프로토타입 기반 언어다. 클래스 기반 언어에서는 "상속"을 사용하지만, 프로토타입 기반 언어에서는 어떤 객체를 원형(prototype)으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻는다.
1.1 constructor, prototype, instance
var instance = new Constructor();
- 어떤 생성자 함수(Constructor)를 new 연산자와 함께 호출하면
- Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스(instance)가 생성된다.
- 이때 instance에는 __proto__ 라는 프로퍼티가 자동으로 부여되는데,
- 이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조한다.
⭐ prototype은 객체이다. 이를 참조하는 __proto__ 역시 객체이다. prototype 객체 내부에는 인스턴스가 사용할 메서드를 저장한다. 그러면 인스턴스에서도 숨겨진 프로퍼티인 __proto__를 통해 이 메서드에 접근할 수 있다.
1.1.1 프로토타입 예제 _ 1번
- Person의 인스턴스는 __proto__ 프로퍼티를 통해 getName을 호출할 수 있다. 왜냐하면 instance의 __proto__가 Constructor의 prototype 프로퍼티를 참조하므로 둘은 같은 객체를 바라보기 때문이다.
var Person = function(name){
this._name = name;
};
Person.prototype.getName = function(){
return this._name;
};
🌞 undefined가 나온 이유?
TyperError가 아닌 undefined가 나왔다. getName은 실제로 실행됐음을 알 수 있고, getName은 함수라는 것이 입증되었다. 다만, 어떤 함수를 메서드로 호출할 때는 메서드명 바로 앞의 객체가 this이다.
즉 Hyemi.__proto__.getName()에서 getName의 함수 내부에서의 this는 Hyemi.__proto__ 라는 객체인 것!
이 객체 내부에는 name 프로퍼티가 없으므로 Error대신 undefined를 반환한다.
var Hyemi = new Person("amy");
Hyemi.__proto__.getName(); //undefined
Person.prototype === Hyemi.__proto__ //true
- 만약 __proto__ 객체에 name 프로퍼티가 있다면? undefined이 아닌 지정한 name 값이 잘 출력된다.
var Hyemi = new Person("amy");
Hyemi.__proto__._name = 'Amy proto is...';
Hyemi.__proto__.getName();
// "Amy proto is..." 출력
- this를 인스턴스로 쓰고 싶다면, __proto__ 없이 인스턴스에서 곧바로 메서드를 쓴다.
var mushroom = new Person('amy', 29);
mushroom.getName(); "amy" 출력
var yangpa = new Person('jenna', 26);
yangpa.getName(); //"jenna" 출력
- 🌟 __proto__는 생략 가능한 프로퍼티이다. 🌟 __proto__를 생략하지 않으면 this는 Hyemi.__proto__를 가리키지만, 이를 생략하면 Hyemi를 가리킨다. this는 Hyemi를 가리키게되어 amy가 출력된다.
Hyemi.__proto__.getName //Amy proto is... 출력
Hyemi.getName // amy 출력
🌞 즉, new 연산자로 Constructor을 호출하면 instance가 만들어지는데, 이 instance의 생략 가능한 프로퍼티인 __proto__는 Constructor의 prototype을 참조한다.
생성자 함수의 prototype에 어떤 메서드나 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티에 접근할 수 있게 된다.
var arr = [1,2];
console.dir(arr);
console.dir(Array);
Array 생성자 함수를 출력한 결과 (좌)와 arr 변수를 출력한 결과 (우)
arr의 __proto__를 열어보니, Array의 prototype과 동일하다. __proto__ 안에는 배열에 사용하는 메서드들이 담겨있다.
Array를 new 연산자와 함께 호출해서 인스턴스를 생성하든, 그냥 배열 리터럴을 생성하든 결과적으로 instance인 [1,2]가 만들어진다. 이 인스턴스의 __proto__는 Array.prototype을 참조하는데, __proto__가 생략 가능하도록 설계되어있기 때문에 인스턴스가 push, pop, forEach 등의 메서드를 내 것처럼 호출할 수 있다.
한편 Array의 prototype 외부에 있는 from, isArray 등의 정적 메서드는 인스턴스가 직접 호출할 수 없고, Array 생성자 함수에서 직접 접근해야한다.
Array.isArray(arr); //true
arr.isArray(); // TypeError
1.2 Constructor 프로퍼티
- 생성자 함수의 프로퍼티인 prototype 객체 내부에는 constructor라는 프로퍼티가 있다. 인스턴스의 __proto__에서도 마찬가지이다. 자기 자신을 참조하는 프로퍼티가 존재하는 이유는 인스턴스로부터 그 원형이 무엇인지 알 수 있는 수단이기 때문이다.
- 인스턴스의 __proto__가 생성자 함수의 prototype 프로퍼티를 참조하며 __proto__가 생략 가능하기 때문에 인스턴스에서 직접 Constructor에 접근할 수 있는 수단이 생긴다.
let arr = [1,2]
Array.prototype.constructor ===Array //true
arr.__proto__.constructor === Array //true
arr.constructor === Array //true
let arr2 = new arr.constructor(3,4)
console.log(arr2) //[3,4]
1.2.1 프로토타입 예제 _ 1번 ( Constructor 변경)
- Constructor는 읽기 전용 속성이 부여된 예외적인 경우(기본형 리터럴 변수- number, string, boolean)를 제외하고 값을 바꿀 수 있다. 모든 데이터가 d instanceof NewConstructor 명령어에 대해 false를 반환한다.
constructor를 변경하더라도 참조하는 대상이 변경될 뿐 이미 만들어진 인스턴스의 원형이 바뀐다거나 데이터 타입이 변하는 것은 아님을 알 수 있다.
어떤 인스턴스의 생성자 정보를 알아내기 위해 constructor 프로퍼티에 의존하는 것이 항상 안전하지는 않다는 것을 알 수 있다.
instance of __
// instanceof 연산자는 생성자의 prototype 속성이 객체의 프로토타입 체인 어딘가 존재하는지 판별
var NewConstructor = function(){
console.log('this is new constructor')
};
var dataType = [
1, // Number & false
'test', // String & false
true, // Boolean & false
{}, //NewConstructor & false
[], //NewConstructor & false
/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
];
dataType.forEach(function(d){
d.constructor = NewConstructor
console.log(d.constructor.name, '&', d instanceof NewConstructor)
})
마지막으로,
🌈 다음 각 줄은 모두 동일한 대상을 가리킨다.
[Constructor]
[instance].__proto__.constructor
[instance].constructor
Object.getPrototypeOf([instance]).constructor
[Constructor].prototype.constructor
🌈 다음 각 줄은 모두 동일한 객체에 접근할 수 있다.
[constructor].prototype
[instance].__proto__
[instance]
Object.getPrototypeOf([instance])
참고 자료
정재남, 『코어자바스크립트』, 위키북스(2019)
'개발공부 > 자바스크립트' 카테고리의 다른 글
[JavaScript] 스터디 10일차_ 클래스(1) 개념 및 상속 (0) | 2021.07.13 |
---|---|
[JavaScript] 스터디 9일차_ 프로토타입(2) 체인 (0) | 2021.07.12 |
[JavaScript] 스터디 8일차_ 클로저 (3) 활용(부분적용함수, 커링함수) (1) | 2021.07.12 |
[JavaScript] 스터디 7일차_ 클로저 (2) 활용 (0) | 2021.07.01 |
[JavaScript] 스터디 6일차_ 클로저 (1) 개념 및 메모리 관리 (0) | 2021.06.28 |