Prototype : 원형, 근본
프로토타입이란 어떤 객체를 뜻한다.
그 객체는 다른 객체가 해당 객체의 속성과 메소드를 사용할 수 있는 객체이다.
즉, 프로토타입은 객체와 객체 사이에 상속같은 현상이 일어나 상위 객체의 속성에 접근할 수 있는 것을 뜻하고 이것은 생성자함수와 new키워드를 통해 구현된다.
우선 프로토타입이 왜 필요한지 알아보자.
예를 들어 여러 개의 책의 정보를 저장하는 객체들을 만든다고 가정해보자.
book1 = {
name: "어린왕자",
publisher: "우리출판사",
displayInfo: function () {
return "책 제목: " + name + "출판사: " + publisher;
},
};
book2 = {
name: "죄와벌",
publisher: "하나출판사",
displayInfo: function () {
return "책 제목: " + name + "출판사: " + publisher;
},
};
책의 정보를 저장하는 객체 2 개를 만들었다.
두 객체는 모두 name, publisher, displayInfo라는 동일한 키값을 가지고 있다.
이처럼 유사한 객체를 여러 개 만들어야 하는 경우에는 생성자함수와 new 키워드를 통해 보다 간편하게 구현할 수 있다.
function Book (name, publisher) {
this.name = name,
this.publisher = publisher,
displayInfo: function () {
return "책 제목: " + this.name + ", 출판사: " + this.publisher;
},
};
var book1 = new Book("어린왕자", "우리출판사");
var book2 = new Book("죄와벌", "하나출판사");
book1.displayInfo() // 책 제목: 어린왕자, 출판사: 우리출판사
생성자함수 Book을 정의하고 new 키워드를 사용하여 두 개의 객체를 간편하게 정의했다.
(지금은 두 개의 객체만을 정의해서 이전의 예제코드와 비교했을 때 코드의 효율성이 크게 돋보이지는 않지만 책(객체)의 개수가 더 많아진다면 이 코드가 좀 더 효율적이라고할 수 있을 것 이다.)
물론 이전의 예제코드에 비해 효율적이 되었지만, 자바스크립트의 메모리 사용의 관점에서 위 코드를 본다면,
책 하나 당 name의 값을 저장할 메모리 100개 +
publisher의 값을 저장할 메모리 100개 +
displayInfo 메서드를 저장할 메모리 100개 + α...
( ※ 실제와는 아주아주 다른 계산이겠지만 이해를 위해 대략적으로 계산해보았다 )
여기서 name과 publisher의 값은 책 하나마다 고유한 값을 가지고있기 때문에 값 하나 당 메모리공간 하나씩 지정하는 로직을 사용할 수밖에 없다.
그러나 모든 객체에 똑같이 들어가는 displayInfo메서드의 경우에는 한 객체에 담아두고 그 값이 필요할 때마다 참조하게 된다면 좀 더 효율적일 것 같다.
이를 위해 프로토타입 개념이 등장한다.
function Book (name, publisher) {
this.name = name,
this.publisher = publisher,
};
Book.prototype.displayInfo = function () {
return "책 제목: " + name + ", 출판사: " + publisher;
}
var book1 = new Book("어린왕자", "우리출판사");
var book2 = new Book("죄와벌", "하나출판사");
book1.displayInfo() // 책 제목: 어린왕자, 출판사: 우리출판사
위의 코드를 보면 Book이라는 생성자함수를 정의해주고 Book의 prototype 속성에 diaplayInfo()메서드를 정의해주었다.
이렇게 하면 book1객체 내부에 displayInfo메서드를 정의하지 않아도 Book의 prototype객체에 displayInfo메서드가 정의되어있으면 마치 book1이 소유하고있는 메서드인 양 사용할 수 있게 된다.
- 모든 함수는 prototype 객체를 소유하고있다.
* 화살표함수는 제외
function Person (name) {
this.name = name,
this.age = age,
this.logName = function () {
console.log("Name: " + name);
},
};
Person이라는 생성자함수를 정의해주었다.
이 생성자함수와 프로토타입 객체의 관계를 표현하면 다음과 같다.
화살표 함수를 제외한 모든 함수가 생성되면 해당 함수와 연결되는 Prototype Object가 같이 생성된다.
Person.prototype.logAge = function () {
console.log("age: " + age);
};
Person의 프로토타입 객체에 logAge메서드를 정의했다.
여기서 내가 개인적으로 많이 헷갈렸던 개념은 "생성자함수 내에서 정의한 속성과 메서드"와 "생성자함수의 프로토타입 객체에서 정의한 속성과 메서드"가 서로 다르다는 것이다.
Person생성자함수를 이용해 인스턴스 객체를 만들면 위 두 속성과 메서드 모두 똑같은 방법으로 접근할 수 있다.
다만 전자는 인스턴스 객체 내에 저장된 속성과 메서드이고, 후자는 인스턴스 객체를 만든 생성자 함수의 프로토타입 객체 내에 저장된 속성과 메서드이다
그래서 위 그림에서 보면 logName은 아직 어떤 객체에도 속하지 않은 상태이지만 logAge는 Person 생성자함수의 프로토타입 객체에 속해있는 상태이다...........🤢우웩...
person1 = new Person("eunbin", 24);
Person 생성자함수를 이용해 person1인스턴스 객체를 만들었다.
지금까지 작성한 코드를 확인해보자!!
function Person (name, age) {
this.name = name,
this.age = age,
this.logName = function () {
console.log("Name: " + name);
},
};
Person.prototype.logAge = function () {
console.log("age: " + age);
};
person1 = new Person("eunbin", 24);
Person을 생성자함수로써 만든 객체 인스턴스 person1은 이 함수의 프로토타입 객체(Prototype Object)에 있는 속성과 메소드에 별다른 명령 없이 접근할 수 있게 된다.
person1.logName() // "eunbin"
person1.logAge() // 24
1번코드 : person1 객체내부에 logName메서드가 정의되어있으므로 당연히 접근 가능하다.
2번 코드 : person1 객체를 생성한 생성자함수 Person의 Prototype Object내에 정의된 logAge 또한 접근 가능하다.
이것이 어떻게 가능할까?
생성자함수와 그 인스턴스 객체의 관계를 가족관계로 비유해보자면 다음과 같다.
생성자함수 호머심슨이 생성되면 그의 아내 프로토타입 오브젝트인 마지심슨이 그와 연결되어 함께 생성된다.
이 둘의 아들 바트심슨은 본인에게 없는 logAge메서드가 호출되었을 때 엄마인 마지심슨에게 요청해서 메서드를 실행할 수 있게 된다.
그림을 보면 인스턴스객체인 바트심슨 내부의 __proto__를 통해 엄마 마지심슨에게 메서드를 요청하는 것을 볼 수 있다.
즉, 인스턴스 객체(바트 심슨) 내부의 __proto__속성은 하나의 객체이고 그것은 해당 인스턴스 객체를 생성한 생성자함수(호머 심슨)의 프로토타입 객체(마지 심슨)를 참조한다.
person1객체의 내부살펴보자.
console.log(person1);
객체 내부에는 age, logName(), name, __proto__ 속성이 들어있다.
__proto__속성을 펼쳐보자.
Person 생성자함수의 프로토타입 객체의 내부를 살펴보자.
console.log(Person.prototype);
Person 생성자함수를 통해 만들어진 인스턴스 객체 person1은 __proto__속성을 이용해 Person의 프로토타입 객체를 참조한다는 것을 알 수 있다.
*person1.__proto__ === Person.prototype
Person의 프로토타입 객체에는 logAge메서드, constructor, __proto__속성이 있다.
- logAge 메서드는 위 코드에서 직접 정의한 메서드이다.
- constuctor은 해당 프로토타입 객체의 생성자를 가리킨다. (가족 비유에 따르면, 남편을 말한다!)
→ 해당 프로토타입의 생성자함수는 Person이므로 constructor는 Person 함수이다.
- __proto__는 해당 객체를 만든 생성자함수의 프로토타입 객체를 참조한다.
- 자바스크립트의 모든 객체는 생성자함수를 통해 만들어진다고 볼 수 있다.
(완전히 정확한 진술은 아니지만 프로토타입을 이해하는 데 있어서는 위와 같이 생각하는 것이 쉽다.)
person1객체는 우리가 직접 작성한 생성자함수와 new 키워드를 통해 만들어진 인스턴스 객체이다.
다른 모든 객체들도 자바스크립트 내부의 생성자함수를 통해 만든어진 인스턴스 객체이다.
숫자나 문자열 등의 데이터들도 객체는 아니지만 자바스크립트 내부적으로 생성자함수를 통해 만들어지는 것 같이 동작한다.
다음 코드를 보자.
var obj = {};
// var obj = new Object();
var func = function () {};
// var func = new Function();
var arr = [];
// var arr = new Array();
var num = 3;
// var num = new Number(3);
var str = “abc”;
// var str = new String(“abc”);
var bool = true;
// var bool = new Boolean(true);
위 코드들은 모두 주석 코드들과 동일하다.
- 즉, 프로토타입 객체도 객체이고 이 객체도 생성자함수를 통해 생성되었다.
이 프로토타입 객체의 생성자함수는 Object이다.
Object함수도 마찬가지로 프로토타입 객체가 존재하고 마지심슨객체는 __proto__ 속성을 통해 해당 프로토타입 객체에 접근할 수 있다.
Object 함수의 프로토타입 객체에도 __proto__가 존재한다!
Object함수의 프로토타입 객체를 생성한 함수가 또 존재하는걸까?
아니다. 이 값은 Null이다.
지금까지의 내용을 정리하면 다음과 같다.
자바스크립트의 모든 객체는 생성자함수를 통해 만들어졌다.
화살표 함수를 제외한 모든 (생성자)함수에는 그와 연결된 프로토타입 오브젝트가 존재한다.
생성자 함수를 통해 생성된 인스턴스 객체는 생성자함수의 프로토타입 오브젝트를 참조한다.
프로토타입 오브젝트도 객체이므로 그를 생성한 생성자함수가 존재하고 그와 연결된 프로토타입 오브젝트가 존재한다.
모든 객체의 프로토타입 최상위에는 Object 함수의 프로토타입 객체가 있다.
Object 함수의 프로토타입 객체는 Null이다.
<최종 가족 관계도>
- person1에서 logName메소드를 실행하면,
person1.logName(); // Name: eunbin
person1객체 내부에 정의되어있는 메소드이므로 person1객체 내부의 메소드를 실행한다.
- person1에서 logAge메소드를 실행하면,
person1.logAge(); // Age: 24
person1객체 내부에는 logAge메서드가 없다
→ person1객체의 생성자함수인 Person의 프로토타입 객체 내부에서 logAge 메소드를 찾아 실행한다.
- person1내부에서 logHeihgt메소드를 실행하면,
person1.logHeight(); // undefined
person1객체 내부에는 logHeight메서드가 없다.
→ person1 객체의 생성자함수인 Person의 프로토타입 객체 내부에도 logHeight 메소드가 없다.
→ Person의 프로토타입 객체의 생성자함수인 Object함수의 프로토타입 객체 내부에도 logHeight 메소드가 없다.
→ undefined 출력.
즉, 객체의 A메서드에 접근하려하면
1. 해당 객체 내부에 A메서드가 존재하는 지 확인한다.
없으면,
2. 해당 객체를 생성한 생성자함수와 연결된 프로토타입 객체 내부에 A메서드가 존재하는지 확인한다.
없으면,
3. 해당 프로토타입 객체를 생성한 생성자함수와 연결된 프로토타입 객체 내부에 A메서드가 존재하는지 확인한다.
없으면,
.
.
.
해당 프로토타입 객체를 생성한 함수인 Object와 연결된 프로토타입 객체 내부에 A메서드가 존재하는지 확인한다.
없으면,
undefined 반환
이를 프로토타입 체인이라 한다.
'Programming > JavaScript' 카테고리의 다른 글
[Javascript] Promise (0) | 2021.05.28 |
---|---|
[JavaScript] 객체 내에 존재하는 속성에 접근하기 (0) | 2021.04.19 |
[JavaScript] sort() 메소드를 이용한 배열 정렬 (0) | 2021.04.02 |
[JavaScript] 원시값과 참조값 (Primitive & Reference) (2) | 2021.03.30 |
[Javascript] Executive context(실행 콘텍스트) (0) | 2021.03.30 |