자바스크립트는 프로토타입을 기반으로 상속을 구현한다.
이 프로토타입이 어떤 것인지 알아보자.
function Circle(radius) {
this.radius = radius;
this.getArea = function () {
return Math.PI * this.radius ** 2;
};
}
const c1 = new Circle(1);
const c2 = new Circle(2);
위 코드의 c1과 c2 객체는 모두 각각 radius 프로퍼티와 getArea 메서드를 갖는다.
getArea 메서드가 각 객체마다 존재할 필요가 있을까?
메모리 낭비다. 하나만 생성해서 모든 인스턴스가 공유해서 사용하는게 더 올바르다.
이걸 상속을 통해서 중복을 제거해보자.
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.getArea = function () {
return Math.PI * this.radius ** 2;
};
const c1 = new Circle(1);
const c2 = new Circle(2);
위 코드에서 getArea 는 단 하나만 생성되어 Circle.prototype 의 메서드로 할당되었다.
프로토타입 객체
한줄 요약해보자. 프로토타입은 '근본' 이다.
어떤 객체의 부모 객체 역할을 하는 객체이다.
모든 객체는 [[Prototype]] 이라는 내부 슬롯을 가진다.
이 값은 프로토타입의 참조다.
결국 [[Prototype]] 내부 슬롯은 내 근간이 누구인가에 대한 답을 가지고 있는 것이다.
__prototype__ 접근자 프로퍼티
모든 객체는 __proto__ 접근자 프로퍼티를 통해 [[Prototype]] 에 간접 접근할 수 있다.
근데 왜 직접 접근하면 안되는 걸까?
[[Prototype]] 의 값에 접근하기 위해 굳이 __proto__ 접근자 프로퍼티를 사용하는 이유는,
상호 참조에 의해 프로토타입 체인이 생성되는 것을 막기 위해서다.
const parent = {};
const child = {};
child.__proto__ = parent;
parent.__proto__ = child; // TypeError: Cyclic __proto__ value
프로토타입 체인은 단방향 링크드 리스트로 구현되어야한다.
따라서 체크 없이 무조건 프로토타입을 교체할 수 없도록 __proto__ 접근자 프로퍼티를 통해서
프로토타입에 접근하고 교체하게끔 구현되어 있는 것이다.
사실 __proto__ 접근자 프로퍼티를 직접 사용하는 것은 권장되지 않는 방식이다.
함수 객체의 prototype 프로퍼티
함수 객체만 갖고 있는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
함수가 아닌 일반 객체는 prototype 프로퍼티가 없다!
또한 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리키기 때문에,
생성자 함수로서 호출할 수 없는 화살표 함수와 메서드 축약 표현(function 키워드 없음)으로 정의한 메서드는 prototype 프로퍼티를 갖지 않는다.
모든 객체가 가지고 있는 __proto__ 접근자 프로퍼티랑 함수 객체만 갖고 있는 prototype 프로퍼티는 사실 같다.
좀 의아할 수 있지만 사용하는 주체가 다르다는게 차이점이다.
__proto__ 접근자 프로퍼티는 모든 객체가 사용 주체이나 prototype 프로퍼티는 생성자 함수가 사용 주체라는 점을 기억해두자!
function Person(name) {
this.name = name;
}
const me = new Person('Hong');
console.log(Person.prototype == me.__proto__); //true
모든 프로토타입은 constructor 프로퍼티를 갖고,
이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다.
리터럴 표기법에 의해 생성된 객체의 생성자 함수 & 프로토타입
생성자 함수에 의해 생성된 인스턴스는 프로토타입의 constructor 프로퍼티에 의해 생성자 함수와 연결된다.
하지만 리터럴 표기법에 의한 객체 생성방식과 같이
명시적으로 new 연산자와 함께 생성자 함수를 호출해 인스턴스를 생성하지 않는 객체 생성 방식도 있다.
const obj = {};
const add = function (a, b) { return a + b; };
const arr = [1, 2, 3];
물론 위와 같이 리터럴 표기법에 의해 생성된 객체도 프로토타입이 존재한다.
하지만 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정지을 수 없다.
const obj = {};
console.log(obj.constructor === Object); // true
obj 객체는 Object 생성자 함수로 생성된 객체가 아닌 객체 리터럴에 의해 생성되었다.
하지만 obj의 constructor 프로퍼티는 Object 생성자 함수와 연결되어 있다.
책에서는 이 부분이 추상연산과 함께 복잡하게 설명되어 있으나,
결국 결론은 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아니라는 것이다.
함수 객체의 경우 더 명확하게 확인 가능하다.
함수 선언문과 함수 표현식을 평가해 함수 객체를 생성한 것은 Function 생성자 함수가 아니다.
하지만 constructor 프로퍼티를 통해 확인하면 a 함수의 생성자 함수는 Function 생성자 함수다.
function a () {}
console.log(a.constructor == Function);
리터럴 표기법에 의해 생성된 객체도 상속을 위해서 프로토타입이 필요하다.
그렇기 때문에 리터럴 표기법에 의해 생성된 객체도 가상 생성자 함수를 갖는다.
프로토타입은 생성자 함수와 더불어서 생성되고, prototype, constructor 프로퍼티에 의해 연결되어 있기 때문이다.
결론적으로 프로토타입과 생성자 함수는 단독으로 존재할 수 없고, 항상 쌍으로 존재한다.
리터럴 표기법 | 생성자 함수 | 프로토타입 |
객체 리터럴 | Object | Object.prototype |
함수 리터럴 | Function | Function.prototype |
배열 리터럴 | Array | Array.prototype |
정규 표현식 리터럴 | RegExp | RegExp.prototype |
프로토타입 생성 시점
프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다.
생성자 함수는 사용자가 직접 정의한 함수와 자바스크립트가 기본으로 제공하는 빌트인 생성자 함수로 구분 가능하다.
사용자 정의 생성자 함수
생성자 함수로서 호출할 수 있는 함수는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
console.log(Person.prototype); // {constructor: f}
function Person (name) {
this.name = name;
}
함수 선언문은 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행된다.
그때 동시에 프로토타입도 더불어 생성된다고 알고 있으면 된다.
생성된 프로토타입은 오직 constructor 프로퍼티만 갖고 있는 객체다.
생성된 프로토타입의 프로토타입은 언제나 Object.prototype 이다.
결국 사용자 정의 생성자 함수는 자신이 평가되어 함수 객체로 생성되는 시점에 프로토타입도 더불어서 생성되며,
생성된 프로토타입의 프로토타입은 언제나 Object.prototype 이다.
빌트인 생성자 함수와 프로토타입 생성 시점
Object, String .. 등의 빌트인 생성자 함수도 일반 함수와 마찬가지로
빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성된다.
객체가 생성되기 이전에 생성자 함수와 프로토타입은 이미 객체화되어서 존재한다.
이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면
프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당된다.
'Archive > Develop' 카테고리의 다른 글
[ 모던 자바스크립트 스터디 ] 실행 컨텍스트 (1) | 2022.10.12 |
---|---|
[ 모던 자바스크립트 스터디 ] 프로토타입 - 2 (0) | 2022.10.02 |
[ 모던 자바스크립트 스터디 ] strict mode (0) | 2022.10.02 |
[ 모던 자바스크립트 스터디 ] 왜 __proto__ 접근자 프로퍼티를 통해 접근하는가? (0) | 2022.10.02 |
[ NestJS ] 의존성 문제 발생 케이스 기록 (0) | 2022.09.28 |