# 🌸19장. 프로토타입

```
👉 자바스크립트는 명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어이다.
```

* `원시 타입(primitive)` 의 값을 제외한 나머지 값들( `함수, 배열, 정규표현식 등` )은 모두 객체다.
* 자바스크립트만의 `프로토타입` 기반의 객체지향 프로그래밍이 존재한다.

## ✅19.1 객체지향 프로그래밍

```
객체지향 프로그래밍이란, 여러 개의 독립적 단위, 즉 **객체의 집합**으로 프로그램을 표현하려는 프로그래밍 패러다임
```

**추상화(abstraction)**

* 객체의 다양한 속성 중에서 프로그램에 필요한 속성만 간추려 표현하는 것

**객체**

* 정의1. 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조
* 정의2. 상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조
* 객체는 2가지 요소로 구성된다.
  * `상태(state)` 를 나타내는 `프로퍼티(property)`
  * 상태를 조작하는 `동작(behavior)` 을 표현하는 `메서드(method)`

## ✅19.2 상속과 프로토타입

```
상속(inheritance) : 어떤 객체의 프로퍼티나 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것
```

[생성자 함수](https://til-3.gitbook.io/bosun-kwaks-til/deep-dive/17-constructor-function#id-17.2)에서 살펴봤듯이, 생성자 함수는 동일한 프로퍼티 구조를 갖는 객체를 여러 개 생성할 때 유용하다.

하지만, 다음과 같은 문제가 있다.

‘Circle’객체 : 반지름, 원의 넓이를 구하는 메서드로 구성되어있다고 하자.

객체에 대해 각각의 원은 각기 다른 반지름을 가질 수 있지만, 원의 넓이를 구하는 메서드는 반지름 정보만 있으면 계산은 동일하다.

생성자 함수로 객체를 여러개 생성하면, 이러한 메서드도 계속해서 생성해서 문제가 생긴다.

→ 원이라는 객체에 대해 반지름은 독립적으로, 원의 반지름을 통해 구할 수 있는 정보(원의 넓이)는 공유하는 것이 효율적이다.

이를 가능하도록 하는 것이 \*\*`상속`\*\*이다.

**자바스크립트는 프로토타입(prototype)을 기반으로 상속을 구현한다.**

```jsx
// 생성자 함수
function Circle(radius) {
    // 각각의 원 반지름 데이터(상태)는 독립적
    this.radius = radius;
}

// 원의 넓이를 구하는 메서드는 Circle 객체의 프로토타입에 등록하여 공유
// 프로토타입은 Circle 생성자 함수의 prototype 프로토타입에 바인딩되어 있다.
Circle.prototype.getArea = function () {
    return Math.PI * this.radius ** 2;
};

// 인스턴스 생성
const circle1 = new Circle(1);
const circle2 = new Circle(2);

// Circle 생성자 함수가 생성한 모든 인스턴스는
// 부모 객체 역할을 하는 프로토타입 Circle.prototype으로부터 메서드를 상속받는다.
console.log(circle1.getArea === circle2.getArea); // true (Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메서드를 공유한다. )

console.log(circle1.getArea()); // 3.141592653589793
console.log(circle2.getArea()); // 12.566370614359172
```

* 생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입(⇒ 상위(부모) 객체 역할을 하는 Circle.prototype)의 모든 프로토타입과 메서드를 상속받는다.
* 생성자 함수가 생성할 모든 인스턴스는 부모 객체인 프로토타입의 자산을 공유하여 사용할 수 있다. 따라서, 생성자 함수가 생성할 모든 인스턴스가 공통적으로 사용할 프로퍼티나 메서드를 프로토타입에 미리 구현해두면 `코드 재사용` 측면에서 유용하다.

## ✅19.3 프로토타입 (객체)

```
모든 객체는 하나의 프로토타입을 갖는다.
그리고 모든 프로토타입은 생성자 함수와 연결되어있다.
```

* 따라서 `객체 - 프로토타입 - 생성자함수`는 연결되어 있다.
  * 프로토타입은 자신의 `constructor 프로퍼티`를 통해 **생성자 함수**에 접근할 수 있고, 생성자함수는 자신의 `prototype 프로퍼티`를 통해 **프로토타입**에 접근할 수 있다.
* 모든 객체는 \[\[Prototype]]이라는 내부 슬롯을 가진다.

\\

**📌 \[\[Prototype]] 내부 슬롯**

* 자신의 프로토타입 = 객체가 자신의 \[\[Prototype]]을 통해 가리키는 프로토타입
* \[\[Prototype]] 내부 슬롯의 값은 `프로토타입의 참조`다.
* \[\[Prototype]]에 저장되는 프로토타입은 `객체 생성 방식`에 의해 결정된다.
  * (null일 수 있음)
  * ex) 객체 리터럴(`{}`)로 생성된 객체 프로토타입 → `Object.prototype`
  * ex) 생성자 함수에 의해 생성된 객체 프로토타입 → `생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체`
* \[\[Prototype]] 내부 슬롯에는 직접 접근할 수 없고 `__prototype__` 접근자 프로퍼티를 통해 자신의 프로토타입에 간접적으로 접근할 수 있다.

### `__proto__` 접근자 프로퍼티

```
모든 객체는 __prototype__ 접근자 프로퍼티를 통해 자신의 프로토타입에 간접적으로 접근할 수 있다.
```

* 내부 슬롯은 프로퍼티가 아니기 때문에, \[\[Prototype]] 내부 슬롯에도 직접 접근할 수 없고 `__proto__` 접근자 프로퍼티를 통해 간접적으로 접근할 수 있다.

**`__proto__` 접근자 프로퍼티는 상속을 통해 사용된다.**

* `__proto__` 접근자 프로퍼티는 Object.prototype의 프로퍼티다.
* 모든 객체는 상속을 통해 Object.prototype**proto** 접근자 프로퍼티를 사용할 수 있다.

**`__proto__` 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유**

* 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서다.
* 서로가 자신의 프로토타입이 되는 비정상적인 프로토타입 체인 예제

  ```jsx
  const parent = {};
  const child = {};

  child.__proto__ = parent;
  parent.__proto__ = child; // TypeError: Cyclic __proto__ value
  ```
* 프로토타입 체인은 `단방향 링크드 리스트`로 구현되어야 한다.
  * 프로퍼티 검색 방향이 한쪽 방향으로만 흘러가야 한다.

**`__proto__` 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.**

* 모든 객체가 `__proto__` 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문이다. → `"직접 상속"`
* 따라서, `__proto__` 접근자 프로퍼티 대신

  1. 프로토타입의 참조를 취득하고 싶은 경우 → `Object.getPrototypeOf` 메서드 사용
  2. 프로토타입을 교체하고 싶은 경우 → `Object.setPrototypeOf`메서드 사용

  ```jsx
  const obj = {};
  const parent = { x: 1 };

  // Object.getPropertyOf 메서드로 obj 객체의 프로토타입 취득
  console.log(Object.getPrototypeOf(obj)); // [Object: null prototype] {}

  // Object.setPrototypeOf 메서드롤 obj 객체의 프로토타입을 교체
  Object.setPrototypeOf(obj, parent);
  console.log(obj.__proto__); // { x: 1 }
  ```

### 함수 객체의 prototype 프로퍼티

**함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.**

* 생성자함수로서 호출할 수 없는 화살표함수와 ES6 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않고 프로토타입도 생성하지 않는다.

  ```jsx
  // 함수 객체는 protoype 프로퍼티를 가진다.
  function () {}.hasOwnProperty("prototype"); // true

  // 일반 객체는 prototype 프로퍼티를 가지지 않는다.
  ({}.hasOwnProperty("prototype"); // false
  ```

**모든 객체가 가지고 있는 `__prototype__` 접근자 프로퍼티와 함수 객체만이 가지고 있는 `prototype 프로퍼티`는 동일한 프로토타입을 가리킨다.**

```jsx
function Person(name) {
    this.name = name;
}
const me = new Person("Kwak");

console.log(me.__proto__ === Person.prototype); // true
```

* 다만 사용하는 주체가 다르다.

| **구분**             | **소유**      | **값**      | **사용 주체** | **사용 목적**                                   |
| ------------------ | ----------- | ---------- | --------- | ------------------------------------------- |
| **proto** 접근자 프로퍼티 | 모든 객체       | 프로토타입의 참조값 | 모든 객체     | 객체가 자신의 프로토타입에 접근 또는 교체하기위해 사용              |
| prototype 프로퍼티     | constructor | 프로토타입의 참조값 | 생성자 함수    | 생성자 함수가 자신이 생성할 객체(인스턴스)의 프로토타입을 할당하기 위해 사용 |

### 프로토타입의 constructor 프로퍼티와 생성자 함수

* 모든 프로토타입은 constructor 프로퍼티를 갖는다.
* constructor 프로퍼티는 prototype 프로퍼티로 자신이 참조하고 있는 생성자 함수를 가리킨다.
* 이 연결은 생성자 함수가 생성될 때(=함수 객체가 생성될 때) 이뤄진다.

```jsx
function Person(name) {
    this.name = name;
}

const me = new Person("Kwak");

// me 객체의 생성자 함수는 Person이다.
console.log(me.constructor === Person); // true
```

📌 **생성자 함수에 의해 생성된 인스턴스는 프로토타입의 constructor 프로퍼티에 의해 생성자 함수와 연결된다.**
