코딩을 쉽게 해보자

[Deep Dive] 19장 - 프로토타입 본문

JavaScript

[Deep Dive] 19장 - 프로토타입

꿀단지코딩 2022. 11. 4. 08:01

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

 

클래스

ES6에서 도입
클래스도 함수이며, 기존 프로토타입 기반 패턴의 Syntactic sugar이라고 볼 수 있다.
클래스와 생성자 함수는 모두 프로토타입 기반의 인스턴스를 생성하지만 정확히 동일하게 동작 X
생성자 함수보다 엄격하게 동작
생성자 함수에서는 제공하지 않는 기능도 제공

새로운 객체 생성 메커니즘으로 보는 것이 좀 더 합당하다고 할 수 있다.

 

1. 객체지향 프로그래밍

객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 말한다.

추상화

다양한 속성 중에서 프로그램에 필요한 속성만 간추려 내어 표현하는 것

 

객체지향 프로그래밍은

객체의 상태(state)를 나타내는 데이터와

상태 데이터를 조작할 수 있는 동작(behavior)을 하나의 논리적인 단위로 묶어 생각한다.

따라서 객체

상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조라고 할 수 있다.

 

2. 상속과 프로토타입

상속이란, 

객체지향 프로그래밍의 핵심 개념으로

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

 

 JS는 프로토타입을 기반으로 상속을 구현, 불필요한 중복 제거

// 생성자 함수
function Circle(radius) {
  this.radius = radius;
  this.getArea = function () => {
    retun Math.PI * this.radius ** 2;
  };
}

const circle1 = new Circle(1);
const circle1 = new Circle(2);

// Circle 생성자 함수는 인스턴스를 생성할 때마다 동일한 동작을 하는
// getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다.
// getArea 메서드는 하나만 생성하며 모든 인스턴스가 공유해서 사용하는 것이 바람직하다.
console.log(circle1.getArea === circle2.getArea); // false

 radius. 프로퍼티 값은 일반적으로 인스턴스마다 다르지만

getArea 메서드는 모든 인스턴스가 동일한 내용의 메서드를 사용하므로

단 하나만 생성하여 모든 인스턴스가 공유해서 사용하는 것이 바람직

=> 메모리 낭비

 

상속을 통해 불필요한 중복을 제거할 수 있다.

function Circle(radius) {
  this.radius = radius;
}

// Circle 생성자 함수가 생성한 모든 인스턴스가 getArea 메서드를
// 공유해서 사용할 수 있도록 프로토타입에 추가한다.
// 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩되어 있다.
Circle.prototype.getArea = fuctnion () {
  return Math.PI * this.radius ** 2;
};

const circle1 = new Circle(1);
const circle2 = new Circle(2);

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

자신의 상태를 나타내는 radius 프로퍼티만 개별적으로 소유하고 내용이 동일한 메서드는 상속을 통해 공유하여 사용하는 것이다.

 

3. 프로토타입 객체

프로토타입 객체란 객체지향 프로그래밍의 근간을 이루는 객체 간 상속(inheritance)을 구현하기 위해 사용된다.

 

모든 객체는 하나의 프로토타입을 갖는다.

모든 프로토타입은 생성자 함수와 연결되어 있다.

 

3-1. __proto__ 접근자 프로퍼티

모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있다.

 

__proto__는 접근자 프로퍼티다.

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

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

- 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서다.

- 프로토 타입은 단방향 링크드 리스트로 구현되어야 한다.

__proto 접근자 프로퍼티를 코드 내에서 직접 사용하는 것 권장 X

- 모든 객체가 __proto__ 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문이다.

3.2 함수 객체의 prototype 프로퍼티

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

prototype 프로퍼티는 생성자 함수가 생성할 객체(인스턴스)의 프로토타입을 가리킨다.

생성자 함수로 호출할 수 없는 함수(non-constructor)인

화살표함수, ES6 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티 소유 X  생성 X

 

모든 객체가 가지고 있는(엄밀히 말하면 Object.prototype으로부터 상속받은) __proto__ 접근자 프로퍼티와 함숫 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킨다.

 

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

모든 프로토타입은 constructor 프로퍼티를 갖는다. 이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다.

 

4. 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입 

constructor 프로퍼티가 가리키는 생성자 함수는 인스턴스를 생성한 생성자 함수다.

프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍(pair)으로 존재한다.

 

프로토 타입의 constructor 프로퍼티를 통해 연결되어 있는 생성자 함수를

리터럴 표기법으로 생성한 객체를 생성한 생성자 함수로 생각해도 크게 무리는 없다.

 

5. 프로토타입의 생성 시점

프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다.

 

5-1 사용자 정의 생성자 함수와 프로토타입 생성 시점

생성자 함수로서 호출할 수 있는 함수, 즉 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.

=> non-constructor는 프로토타입이 생성되지 않는다.

 

5-2 빌트인 생성자 함수와 프로토타입 생성 시점

객체가 생성되기 이전에 생성자 함수와 프로토타입은 이미 객체화되어 존재한다.

이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당된다.

 

6. 객체 생성 방식과 프로토타입의 결정

객체는 다음과 같이 다양한 생성 방법이 있다.

  • 객체 리터럴
  • Object 생성자 함수
  • 생성자 함수
  • Object.create 메서드
  • 클래스(ES6)

 

7. 프로토타입 체인

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

Person.prototype.sayHello = function () {
  console.log(`Hi! My name is ${this.name}`);
};

const me = new Person('Lee');

// hasOwnProperty는 Object.prototype의 메서드다.
console.log(me.hasOwnProperty('name')); // true

me 객체가 Person.prototype 뿐만 아니라 Object.prototype도 상송받았다는 것을 의미한다.

me 객체의 프로토타입은 Person.prototype이다.

 

자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다.

이를 프로토타입 체인이라 한다.

프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 메커니즘이다.

 

Object.prototype을 프로토타입 체이느이 종점(end of prototype chain)이라 한다.

프로토타입 체인은 상속과 프로퍼티 검색을 위한 메커니즘

스코프 체인은 식별자 검색을 위한 메커니즘

 

스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용된다.

 

8. 오버라이딩과 프로퍼티 섀도잉

상속 관계에 의해 프로퍼티가 가려지는 현상을 프로퍼티 섀도잉이라 한다.

 

오버라이딩

상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식이다.

오버로딩

함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식
JS는 오버로딩을 지원하지 않지만 arugments 객체를 사용하여 구현할 수는 있다.

 

9. 프로토타입의 교체

프로토타입은 생성자 함수 또는 인스턴스에 의해 교체할 수 있다.

 

 

10. instanceof 연산자

우변의 생성자 함수의 prototype에 바인딩된 객체가

좌변의 객체의 프로토타입 체인 상에 존재하면 true로 평가되고, 그렇지 않은 경우에는 false로 평가된다.

 

11. 직접 상속

11-1 Object.create에 의한 직접 상속

11-2 객체 리터럴 내부에서 __proto__에 의한 직접 상속

 

12. 정적 프로퍼티/메서드

생성자 함수 객체가 소유한 프로퍼티/메서드를 정적 프로퍼티/메서드라고 한다.

정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다.

 

13. 프로퍼티 존재 확인

13-1 in 연산자

13-2 Object.prototype.hasOwnproperty 메서드

 

14. 프로퍼티 열거

14-1. for ... in 문

for ... in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열거(enumeration)한다.

 

14-2. Object.keys/values/entries 메서드