이번 글은 '何となくJavaScriptを書いていた人が1歩先に進むための本(아무 생각 없이 JavaScript를 작성하던 사람이 한 걸음 더 나아가기 위한 책)의 Chapter 5. プロトタイプを知っておこう!(프로토타입을 알아보자!)'를 바탕으로 작성하였습니다.
프로토타입이란?
JavsScript에서 '프로토타입'이란 약간 어폐가 있긴 하지만 아무튼 '클래스(Class)의 대신'이라고 생각해두자. 원래 JavaScript에는 '클래스'라는 개념(※)이 없으므로 그 대신 이 '프로토타입을 구사하여 객체 지향과 유사한 기능'을 구현한다.
※ ES6(ECMAScript6)에 와서야 도입되었다.
프로토타입 프로퍼티(Prototype Property)
객체에는 멤버를 추가하기 위한 프로토타입 프로퍼티라는 것이 존재한다. 프로토타입 프로퍼티는 특별히 의식해서 쓰지 않는 한 빈 객체를 참조하는데 이 프로퍼티에 어떠한 요소를 담을 경우 객체를 인스턴스화하여 앞선 객체에 따라 쓰는 것이 가능하다.
역시 말로 하면 어렵기에 예를 살펴보겠다.
// 예)5−2−①
var Phone = function(name) {
this.name = name;
};
Phone.prototype.getName = function() {
return this.name;
};
var featurePhone = new Phone('feature phone');
var smartPhone = new Phone('smart phone');
console.log(featurePhone.getName()); // feature phone
console.log(smartPhone.getName()); // smart phone
프로토타입 프로퍼티를 사용하여 객체에 메소드(Method)를 추가하는 예제이다. 프로퍼티에 담기는 메소드가 인스턴스화한 각각의 객체에 이어진 것을 알 수 있다. 하지만 어째서 굳이 이런 프로퍼티를 사용하여 메소드를 정의하는 것일까. 생성자 안에서 정의하면 안 되는 것일까.
위 질문에 대한 답은 '메모리 사용량을 줄이기 위해서'이다. 인스턴스화한 객체는 '생성자로 정의된 멤버만큼의 메모리 영역을 객체마다 확보'한다. 프로퍼티는 인스턴스마다 사용할 값이 되기에 문제 없으나 '메소드는 인스턴스를 통해 전부 같은 기능을 하기 때문에 개별적으로 메모리를 확보하는 것은 낭비'일 뿐이다. 여기서 프로토타입 프로퍼티가 역할을 한다. '인스턴스화한 객체는 사용한 생성자의 프로토타입 프로퍼티에 대해 암묵적으로 참조를 한다.' 즉, '프로토타입 프로퍼티에 담긴 메소드에도 암묵적인 참조'를 가지기에 굳이 미리 메소드를 가질 필요가 없다. 따라서 불필요한 메모리 소비를 줄일 수 있는 것이다.
또한 프로토타입 프로퍼티를 쓰면 다음과 같은 이점이 있다.
// 예) 5−2−②
var Phone = function(name) {
this.name = name;
};
var featurePhone = new Phone('feature phone');
Phone.prototype.getName = function() {
return this.name;
};
var smartPhone = new Phone('smart phone');
console.log(featurePhone.getName()); // feature phone
console.log(smartPhone.getName()); // smart phone
프로토타입 프로퍼티에 메소드를 추가하기 전에 featurePhone의 생성해 보았다. featurePhone도 getName 메소드를 호출함을 볼 수 있다. 이것도 '인스턴스화한 객체는 사용한 생성자의 프로토타입 프로퍼티에 대해 암묵적인 참조를 가진다'고 생각한다면 특별히 이상한 점은 없다. 즉 프로토타입 프로퍼티로 메소드를 관리한다면 인스턴스가 객체(생성자)의 변경을 실시간으로 인식 가능한 것이다.
특히 메모리를 줄이는 관점에서도 프로토타입 프로퍼티의 가치가 충분히 전해지리라 본다. 앞으로 생성자를 정의할 때에는 '메소드는 반드시 프로토타입 프로퍼티로 철저히 관리'하자.
더욱이 위 예제 코드에서는 프로토타입 프로퍼티의 정의를 메소드 연산자로 수행했는데 같은 일을 객체 리터럴(object literal)을 사용해서도 가능하다. 오히려 실제 사용할 적에는 몇가지 메소드가 필요하기 때문에 리터럴 표기법이 더 바람직할 것이다.
객체 리터럴로 프로토타입 프로퍼티를 정의한 예도 살펴보자.
// 예) 5−2−③
var Phone = function(name,model,color) {
this.name = name;
this.model = model;
this.color = color;
};
Phone.prototype = {
getName : function() {return this.name;},
getModel : function() {return this.model;},
getColor : function() {return this.color;}
}
var featurePhone = new Phone('feature phone', 'X-X', 'black');
var smartPhone = new Phone('smart phone', 'XX-XX', 'red');
console.log(featurePhone.getModel()); // X-X
console.log(smartPhone.getColor()); // red
프로토타입 체인(Prototype Chain)
프로토타입 체인이란 JavaScript로 객체 지향 코드를 설계하기 위해 필요한 구조이다.
예를 통해 살펴보자.
// 예) 5−3−①
var Phone = function() {};
Phone.prototype = {
getOwner : function() { console.log('Owner is' , this.owner); }
};
var SmartPhone = function(owner) { this.owner = owner };
SmartPhone.prototype = new Phone(); ①
SmartPhone.prototype.tap = function() { console.log('tap!') };
var myPhone = new SmartPhone('igarashi');
myPhone.getOwner(); ② // Owner is igarashi
myPhone.tap(); // tap!
var otherPhone = new SmartPhone('tarama');
otherPhone.getOwner(); // Owner is tarama
otherPhone.tap(); // tap!
이 예제에서 가장 주목할 만한 점은 ① 부분으로 이것이 바로 프로토타입 체인이다. '계승하고 싶은 객체의 인스턴스를 자신의 프로토타입 프로퍼티에 담는' 것이다.
구체적으로 ② 부분에서는 아래와 같이 멤버의 해결이 이루어진다.
- myPhone 객체 자신의 getOwner 메소드를 찾는다.
- getOwner 메소드를 찾을 수 없기 때문에 myPhone의 생성자(SmartPhone 객체)의 프로토타입(Phone 객체의 인스턴스)의 getOwner 메소드를 찾는다.
- Phone 객체의 인스턴스에서도 발견되지 않기 때문에 Phone 객체의 프로토타입에서 getOwner 메소드를 찾는다.
- Phone 객체의 프로토타입에서 getOwner 메소드를 발견한다.
순서를 따라간다면 어렵지 않다.
이 프로토타입 체인을 잘 활용하면 분명 JavaScript에서도 자유자재로 객체 지향적인 코드 작성이 가능할 것이다.
'👩💻 Programming > JavaScript' 카테고리의 다른 글
함수의 달인이 되자_2. 변수(variables)와 클로저(closure) from Secrets of the JavaScript Ninja (0) | 2022.04.11 |
---|---|
함수의 달인이 되자_1. 클로저(closure)와 스코프(scope) from Secrets of the JavaScript Ninja (0) | 2022.04.11 |
함수를 좀더 이해해보자! (0) | 2022.04.07 |
함수에 대한 이해를 넓혀 보자! (0) | 2022.04.07 |
'this'를 갈고 닦자! (0) | 2022.04.07 |
댓글