❓프로토타입이란
자바스크립트는 원시값을 제외한 거의 모든 것이 객체라는 사실은 들어봤을 것이다. 프로토타입은 자바스크립트에서 객체지향을 구현하는데 중요한 매커니즘으로 객체간의 상속을 위해 사용되는 개념이다.
다음 코드를 살펴보자.
function Fruit(num) {
this.num = num
this.price = function () {
return this.num * 500
}
}
const fruit1 = new Fruit(10)
const fruit2 = new Fruit(20)
Fruit 생성자 함수를 통해 생성된 인스턴스들은 모두 각각 num과, price라는 프로퍼티를 갖는다. 하지만 살펴보면 각각의 인스턴스가 다른 num을 가지는 것은 문제가 없지만, price 메서드는 각 인스턴스가 가지는 num 프로퍼티에 500을 곱한 값을 리턴하는 역할을 하기 때문에, 모든 인스턴스가 동일한 내용의 메서드를 가지게 되는 것이나 다름없다. 따라서, 10개의 인스턴스가 생성된다면 같은 내용의 메서드가 10개가 생성되므로 불필요하게 메모리를 낭비한다.
따라서 불필요한 중복을 제거하기 위해 프로토타입을 기반으로 상속을 구현한다.
function Fruit(num) {
this.num = num
}
Fruit.prototype.price = function () {
return this.num * 500
}
const fruit1 = new Fruit(10)
const fruit2 = new Fruit(20)
위의 그림을 통해 알 수 있듯이 생성자 함수를 통해 생성한 모든 인스턴스는 자신의 프로토타입에 존재하는 모든 프로퍼티를 상속받는다.
1️⃣ 객체 생성 방식에 따른 프로토타입
위에서 언급했듯이 자바스크립트는 거의 모든 것이 객체이고, 이 객체는 [[Prototype]]이라는 내부 슬롯을 가진다. 이 때 [[Prototype]]에 저장되는 프로토타입은 객체 생성 방식에 의해서 결정된다. 다시 말하면 객체가 생성될 때 어떤 방식으로 객체가 생성되냐에 따라서 프로토타입이 결정되고 [[Prototype]]에 저장된다.
객체 생성 방식은 다음과 같은 방법들이 존재한다.
1️⃣ 객체 리터럴
2️⃣ Object 생성자 함수
3️⃣ 생성자 함수
4️⃣ Object.create 메서드
5️⃣ 클래스
위의 다양한 객체 생성 방식은 객체 생성 방식에는 차이점이 존재하지만, 모두 추상 연산 OrdinaryObjectCreate에 의해 생성된다는 공통점이 존재한다.
즉, 추상 연산 OrdinaryObjectCreate에 인수로 전달되는 프로토타입을 프로토타입으로 갖는다.
1️⃣ 객체 리터럴 생성 방식
객체 리터럴 방식으로 객체를 생성할 때 추상 연산 OrdinaryObjectCreate를 호출하고 인수로 Object.prototype을 전달한다.
따라서 객체 리터럴 생성 방식을 통해 생성된 객체의 프로토타입은 Object.prototype이다.
2️⃣ Object 생성자 함수
Object 생성자 함수를 통해 객체를 생성할 때 추상 연산 OrdinaryObjectCreate를 호출하고 인수로 Object.prototype을 전달한다.
따라서 Object 생성자 함수 방식을 통해 생성된 객체의 프로토타입은 Object.prototype이다.
위의 두 방식 모두 구조를 보면 동일하게 Object.prototype을 프로토타입으로 가지는 것을 알 수 있다. 위 두가지 방식의 차이점이라고 하면 프로퍼티를 추가하는 방식이다. 객체 리터럴 생성 방식은 객체 리터럴 내부에 프로퍼티를 추가하지만, Object 생성자 함수 방식은 빈 객체 생성 이후 프로퍼티를 추가해야 한다.
3️⃣ 생성자 함수
위의 방식들과 마찬가지로 생성자 함수를 통해 인스턴스를 생성하면 추상 연산 OrdinaryObjectCreate를 호출하는데, 이 때 인수로 해당 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체를 전달한다. 기존 빌트인 생성자 함수와 해당 프로토타입은 이미 빌트인 메서드를 가지고 있지만, 사용자 정의 생성자 함수와 해당 프로토타입은 constructor 프로퍼티만 가진다.
2️⃣ 프로토타입 체인
객체간의 상속을 위해 프로토타입이 사용되는데 그렇다면 프로토타입의 상속은 어떤 개념으로 이루어질까?
지난 포스팅에서 정리했던 스코프 체인과 같이 프로토타입도 체인이 존재한다. 다른 점은 스코프 체인은 식별자 검색을 위한 것이고 프로토타입 체인은 상속과 프로퍼티 검색을 위한 것이다.
자바스크립트는 객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 존재하지 않는다면, [[Prototype]] 내부 슬롯의 참조를 따라 상위 프로토타입의 프로퍼티를 순차적으로 검색하여 상속을 구현한다.
function Foo(num) {
this.num = num
}
const foo1 = new Foo(1)
console.log(foo1)
위의 코드의 결과를 살펴보면 다음과 같다.
여기서 가장 중요한 것은 프로토타입 체인의 종점은 언제나 Object.prototype이고, 따라서 모든 객체는 Object.prototype을 상속받는다는 것이다.
3️⃣ 오버라이딩, 프로퍼티 섀도잉
function Fruit(num) {
this.num = num;
}
Fruit.prototype.price = function () {
console.log(this.num * 1000);
};
const apple = new Fruit(10);
apple.price = function () {
console.log(this.num * 2000);
};
apple.price();
위처럼 생성자 함수 Fruit을 정의하고 해당 생성자 함수의 프로토타입에 price라는 프로퍼티를 추가했다.
이 때 생성자 함수를 통해 인스턴스를 생성하고 인스턴스에 같은 이름을 가진 프로퍼티를 추가한다면 프로토타입 프로퍼티인 price를 덮어씌우는 것이 아니라 생성한 인스턴스의 프로퍼티로 추가하는 것이다. 따라서 price 메서드를 오버라이딩한다. 이 때 상위 프로토타입의 프로퍼티가 가려진것 처럼 동작하기 때문에 이를 프로퍼티 섀도잉이라 한다.