prototype
🧑🏻💻 객체지향 프로그래밍
JS는 프로토타입 기반의 객체지향 프로그래밍을 지원하는 프로그래밍 언어이다.
그 이유는 JS의 원시 타입의 값을 제외한 나머지 값들은 모두 객체(함수, 배열, 정규 표현식 등)이면서, 프로토타입을 기반으로 객체를 생성하고 상속을 구현하기 때문이다.
객체는 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료 구조이다.
객체지향 프로그래밍은 이러한 객체를 객체의 상태를 나타내는 상태 데이터(프로퍼티)와 프로퍼티를 조작할 수 있는 동작(메서드)을 하나의 논리적인 단위로 묶어 생각한다.
🧑🏻💻 상속과 프로토타입
JS는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복을 제거한다.
✅ 상속 전
// 생성자 함수
function Circle(radius) {
this.radius = radius;
this.getArea = function () {
return Math.PI * this.radius ** 2;
};
}
const circle = new Circle(1);
const circle2 = new Circle(2);
console.log(circlel.getArea === circle.getArea); // false
✅ 상속 후
// 생성자 함수
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.getArea = function () {
return Math.PI * this.radius ** 2;
};
const circlel = new Circle(1);
const circle2 = new Circle(2);
console. log(circle.getArea === circle.getArea); // true
prototype 객체
✅ 내부 슬롯 [[ Prototype ]]
모든 객체는 [[ Prototype ]] 이라는 내부 슬롯을 가지며, 이 내부 슬롯의 값은 프로토타입의 참조다.
객체 생성 방식에 따라 프로토타입이 결정되고 [[ Prototype ]] 에 저장된다.
객체 리터럴로 생성된 객체 : Object.prototype
생성자 함수로 생성된 객체 : 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체
✅ prototype
모든 객체는 "하나의 prototype"(prototype = prototype 객체, 생성자 함수의 프로퍼티)을 갖는다. prototype은 생성자 함수.prototype(인스턴스)를 가리킨다.
모든 생성자 함수가 생성할 인스턴스(생성자 함수.prototype)는 constructor 프로퍼티를 갖는다. constructor 프로퍼티는 prototype 프로퍼티로 생성자 함수를 가리킨다.
즉 생성자 함수는 자신의 prototype 프로퍼티를 통해 인스턴스에 접근할 수 있으며, 인스턴스 또한 자신의 constructor 프로퍼티를 통해 생성자 함수에 접근할 수 있다.
생성자 함수 호출용으로 사용하지 않는 일반 함수도 prototype 프로퍼티를 소유하고 있지만 인스턴스를 생성하지 않는 이상 의미가 없다.
✅ __proto__ 접근자
모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, [[ Prototype ]] 내부 슬롯이 가리키는 프로토타입에 간접적으로 접근할 수 있다.
__proto__ 접근자 프로퍼티는 자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수 ([[ Get ]], [[ Set ]]) 프로퍼티 어트리뷰트로 구성된 프로퍼티다.
__proto__ 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아닌 Object.prototype의 프로퍼티로, 모든 객체는 상속을 통해 Object.prototype.__proto__ 를 사용할 수 있다.
__proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유
순환 참조하는 프로토타입 체인이 만들어지면 프로토타입 체인 종점이 존재하지 않기 때문에 프로토타입 체인에서 프로퍼티를 검색할 때 무한 루프에 빠진다. 이를 1차적으로 방지하기 위해 __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하도록 하고있다.
- 모든 객체가 __proto__ 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문에 __proto__ 접근자 프로퍼티를 직접 사용하는 것은 좋지 않다. 프로토타입 참조를 취득할 때는 Object.prototypeOf 메서드를, 프로토타입을 교체하고 싶은 경우에는 Object.setPrototypeOf 메서드를 사용할 것을 권장한다.
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
console.log(Person.prototype === me.__proto__); // true
✅ prototype와 __proto__ 접근자
✏️ 공통점
- 두 프로퍼티 모두 프로토타입을 참조한다.
✏️ 차이점
__proto__ 접근자 프로퍼티는 모든 객체가 소유하고 사용할 수 있지만, prototype 프로퍼티는 생성자 함수(constructor)만 소유하고 사용할 수 있다.
__proto__ 접근자 프로퍼티는 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용합니다. 반면 prototype 프로퍼티는 생성자 함수가 자신이 생성할 인스턴스의 프로토타입을 할당하기위해 사용한다.
참고 자료
- 모던 자바스크립트 Deep Dive (사진 출처)