프로토타입이란
자바스크립트는 객체 간의 상속이 이루어질 때
C언어나 Java와는 다른 방식으로 상속이 이루어진다.
객체를 복사하는 방식으로 상속을 구현하는 다른 언어들과 달리
자바스크립트는 자신만의 속성들 + 프로토타입 객체에 대한 링크를 가진다.
프로토타입은 체인처럼 위에서 아래로 쭉 연결되어 있으며,
자신에게 연결된 프로토타입부터 위로 올라가며
프로토타입 체인의 종단에 이를 때까지 속성을 탐색할 수 있다.
모든 객체가 자기 자신의 속성을 보관하는 주머니와,
부모/조상 객체의 연결 주머니와도 연결된 연결 주머니(prototype)을 가진다고 이해할 수 있다.
이 둘이 혼동되기 쉬운데,
이 둘은 완전히 별개의 두 주머니라고 이해하는 것이
이해가 쉽다.
예를 들어 함수를 작성하고 그 함수를 생성자로 사용하여 인스턴스를 생성해보자.
아래의 경우 sayHello 메서드를 속성으로 직접 집어넣었다.
이렇게 될 경우 인스턴스를 생성할 때마다 sayHello 메서드가 생성되게 된다.
function Person(name){
this.name = name
this.age = 18
this.sayHello = function() {
console.log(`Hi, I'm ${this.name}`);
}
}
const jennie = new Person('Jennie');
console.log(jennie);
/*
Person {
name: 'Jennie',
age: 18,
sayHello: ƒ (),
__proto__: { constructor: ƒ Person() }
}
*/
jennie.sayHello(); //"Hi, I'm Jennie"
위와 달리 아래와 같이 prototype에 메서드를 포함시킬 경우,
인스턴스를 생성할 때마다 메서드를 반복하여 생성하지 않고,
생성된 인스턴스들은 prototype 내의 메서드를 함께 공유하여 사용한다.
또, prototype에 집어넣은 속성(함수)는 인스턴스의 속성(property)으로 나타나지 않지만,
인스턴스가 사용하는 것은 가능하다.
function Person(name){
this.name = name
this.age = 18
}
Person.prototype.sayHello = function(){
console.log(`Hi, I'm ${this.name}`);
}
const jennie = new Person('Jennie');
console.log(jennie);
/*
Person {
name: 'Jennie',
age: 18,
__proto__: { sayHello: ƒ (), constructor: ƒ Person() }
}
*/
jennie.sayHello(); //"Hi, I'm Jennie"
jennie 인스턴스가 Person의 prototype 주머니에 연결(link)되어 있기 때문이다.
객체는 자신의 위로 연결된 모든 protytpe의 속성을 꺼내어 쓸 수 있다.
Class 문법을 Prototype으로 작성하기
ECMAScript2015부터 클래스 문법이 자바스크립트에서 사용되기 시작했다.
클래스 문법을 탑재한 자바스크립트로 개발을 시작한 나도
prototype보다 클래스가 훨씬 더 익숙하지만
사실 클래스 문법은 클래스 기반 언어 사용자들에게 익숙하게 사용되기 위한 문법적 변환 역할(syntactic sugar)이 크고
자바스크립트는 여전히 prototype에 의한 상속(메서드를 참조하는 것에 더 가까운)으로 동작하고 있다.
class Greeter1 {
constructor() {
this.message = 'Hello';
}
sayHello() {
console.log(this.message);
}
}
class Greeter2 extends Greeter1 {
constructor() {
super();
this.message = 'Hi';
}
sayHello() {
super.sayHello();
console.log('World');
}
}
const greeter1 = new Greeter1();
greeter1.sayHello();
const greeter2 = new Greeter2();
greeter2.sayHello();
위와 같이 간단한 클래스 상속을 prototype 타입으로 구현해보면 아래와 같다.
function Greeter1() {
this.message = 'Hello';
}
Greeter1.prototype.sayHello = function () {
console.log(this.message);
};
function Greeter2() {
Greeter1.call(this); // 부모 클래스의 생성자 호출
this.message = 'Hi';
}
Greeter2.prototype = Object.create(Greeter1.prototype); // 프로토타입 체인 설정
Greeter2.prototype.constructor = Greeter2; // constructor를 Greeter2로 설정
Greeter2.prototype.sayHello = function () {
Greeter1.prototype.sayHello.call(this); // 부모 클래스의 메서드 호출
console.log('World');
};
const greeter1 = new Greeter1();
greeter1.sayHello();
const greeter2 = new Greeter2();
greeter2.sayHello();
Greeter2가 Greeter1을 상속하게 하기 위해서
Greeter1.prototype을 갖는 객체를 Greeter2의 prototype으로 만든다.
Greeter1의 prototype을 Greeter2의 prototype으로 직접 집어넣게 되면,
Greeter2는 Greeter1의 속성을 상속받는 또다른 생성자 역할을 할 객체가 아니라,
Greeter1의 인스턴스와 같이 prototype을 그대로 받는 객체가 되어 버린다.
예를 들어, Greeter2.prototype = Greeter1.prototype으로 설정하면,
Greeter2.prototype.sayHello 메서드를 수정할 때,
Greeter1.prototype.sayHello 메서드도 동시에 수정된다.
둘이 동일한 prototype 주머니를 공유하게 되는 것이다.
우리가 원하는 것은 그게 아니라,
Greeter1의 prototype 주머니에 Greeter2가 연결성(link)을 갖기를 바란 것이기 때문에,
Greeter1.prototype에 연결성을 갖는 또다른 객체를 prototype으로 삼아야하는 것이다.
Object.create(Greeter1.prototype)으로 객체를 생성하고,
Greeter2.prototype.constructor를 Greeter2로 지정해줌으로써
Greeter2가 생성자 함수의 역할을 할 수 있도록 해준다.
Greeter1.call(this)와 Greeter1.prototype.sayHello.call(this)에 사용된
call(this)는 해당 constructor, 메서드가 실행될 때,
함수를 실행시킴과 동시에
this에 어떤 객체가 매핑되어야 하는지 알려주는 역할이다.
call(this)로 지정해줌으로써 Greeter1의 this.message의 this가
함수를 실행한 그 실행 주체인 객체에 매핑된다.
Prototypal 상속의 장단점
prototype에 의한 상속 방식은
prototype에 대한 연결성을 가짐으로서 코드를 공유하며 접근이 가능하기 때문에
코드를 복사하는 방식이 아니므로 메모리를 절약할 수 있다는 장점이 있으나,
찾고자 하는 속성이 존재하지 않을 때는, 프로토타입 체인의 종단까지 모든 프로토타입을 탐색하게 되어
성능이 저하될 수 있다는 단점이 있다.
'Node.js > JavaScript' 카테고리의 다른 글
[JS문법] 이스케이프 시퀀스 종류 정리 (0) | 2023.08.09 |
---|---|
[js 메서드] 배열.find() (0) | 2023.06.18 |
[js 내장 객체] Map (1) | 2023.06.16 |
[js 내장 객체] Set (0) | 2023.06.16 |
[js 메서드] 배열.sort() (0) | 2023.06.16 |