[모던 자바스크립트 Deep Dive] 19장. 프로토타입 - 1
1. 객체지향 프로그래밍
- 객체의 집합으로 프로그램을 표현하는 프로그래밍 패러다임.
- 추상화(Abstraction) : 필요한 속성만 간추려 내어 표현하는 것.
- 객체 : 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조,
상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조.
2. 상속과 프로토타입.
- 상속(Inheritance) : 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것.
코드의 재사용이란 관점에서 유용. 아래의 예제는 프로토타입을 기반으로 상속을 구현한 예제이다.
function Circle(radius){
this.radius = radius;
}
Circle.prototype.getArea = function() {
return Math.PI * this.radius ** 2;
}
const circle1 = new Circle(1);
const circle2 = new Circle(2);
console.log(circle1.getArea === circle2.getArea) // true
3. 프로토타입 객체
- 객체 간 상속을 구현하기 위해 사용.
1) __proto__ 접근자 프로퍼티
(1) __proto__ 는 접근자 프로퍼티이다.
- 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근 가능.
- 접근자 함수, [[Get]], [[Set]] 프로퍼티 어트리뷰트로 구성된 프로퍼티이다.
- __proto__ 접근자 프로퍼티를 통해 새로운 프로토타입을 할당하면 setter 함수인 [[Set]]이 호출된다.
(2) __proto__ 접근자 프로퍼티는 상속을 통해 사용된다.
- __proto 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티이다.
(3) __proto 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유
- 프로토타입 체은은 단방향 링크드 리스트로 구현되어야 한다. 즉, 프로퍼티 검색 방향이 한쪽 방향으로만 흘러가야 한다.
순환 참조하는 프로토타입 체인이 만들어지면 프로토타입 체인에서 프로퍼티를 검색할 때 무한 루프에 빠지게 되기 때문에 아무런 체크 없이 무조건적으로 프로토타입을 교체할 수 없도록 __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하고 교체하도록 구현되어 있다.
(4) __proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.
- 모든 객체가 __proto__ 접근자 프로퍼티를 사용 가능한 것은 아님.
- 직접 상속을 통해 Object.prototype을 상속받지 않는 객체를 생성할 수 있기 때문에 __proto__ 접근자 프로퍼티를 사용할 수 없음.
- __proto__ 접근자 프로퍼티 대신 프로토타입의 참조를 취득하고 싶다면 Object.getPrototypeOf 메서드를 사용하고, 교체하고 싶다면 Object.setPrototypeOf 메서드를 사용할 것을 권장.
2) 함수 객체의 prototype 프로퍼티
- 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
구분 | 소유 | 값 | 사용 주체 | 사용 목적 |
__proto__ 접근자 프로퍼티 |
모든 객체 | 프로토타입의 참조 | 모든 객체 | 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용. |
prototype 프로퍼티 |
constructor | 프로토타입의 참조 | 생성자 함수 | 생성자 함수가 자신이 생성할 객체(인스턴스)의 프로토타입을 할당하기 위해 사용. |
4. 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입.
- 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재한다.
- 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입.
리터럴 표기법 | 생성자 함수 | 프로토타입 |
객체 리터럴 | Object | Object.prototype |
함수 리터럴 | Function | Function.prototype |
배열 리터럴 | Array | Array.prototype |
정규 표현식 리터럴 | RegExp | RegExp.prototype |
// 객체 리터럴
const obj = {};
// 함수 리터럴
const add = function(a,b) { return a+b; };
// 배열 리터럴
const arr = [1,2,3];
//정규 표현식 리터럴
const regexp = /is/ig;
5. 프로토타입의 생성 시점
- 프로토타입은 생성자 함수가 생성되는 시점에 생성. 생성자 함수는 사용자 정의 생성자 함수와 빌트인 생성자 함수로 구분.
1) 사용자 정의 생성자 함수와 프로토타입 생성 시점
- 자신이 평가되어 함수 객체로 생성되는 시점에 프로토타입도 더불어 생성되며, 생성된 프로토타입의 프로토타입은 언제나 Object.prototype 이다.
생성자 함수로 호출할 수 있는 함수, constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 생성.
생성자 함수로 호출할 수 없는 함수, non-constructor는 프로토타입이 생성되지 않음.
2) 빌트인 생성자 함수와 프로토타입 생성 시점
- 빌트인 생성자 함수가 생성되는 시점에 프로토타입 생성.
- 객체가 생성되기 이전에 생성자 함수와 프로토타입은 객체화되어 존재. 이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당된다!
6. 객체 생성 방식과 프로토타입의 결정
- 객체는 다양한 생성 방법이 있는데 세부적인 방식의 차이는 있으나 추상 연산 Ordinary Object Create 에 의해 생성된다는 공통점이 있음.
1) 객체 리터럴에 의해 생성된 객체의 프로토타입
const obj = { x : 1 };
console.log(obj.constructor === Object); // true
console.log(obj.hasOwnProperty('x')); //true
2) Object 생성자 함수에 의해 생성된 객체의 프로토타입
const obj = new Object();
obj.x = 1;
console.log(obj.constructor === Object); // true
console.log(obj.hasOwnProperty('x')); // true
3) 생성자 함수에 의해 생성된 객체의 프로토타입
function Person(name){
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hi! My name is ${this.name}`);
}
const me = new Person('Lee');
me.sayHello(); // Hi! My name is Lee
7. 프로토타입 체인
- 자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다.
이를 프로토타입 체인이라 한다.
- 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 메커니즘이다.
- Object.prototype은 프로토타입 체인의 종점!, Object.prototype에서도 프로퍼티를 검색할 수 없다면 undefined 반환.
- 프로토타입 체인 : 상속과 프로퍼티 검색을 위한 메커니즘
* 스코프 체인 : 식별자 검색을 위한 메커니즘