19 프로토타입

Prototype : [명사] 원형 (原型) : 같거나 비슷한 여러 개가 만들어져 나온 본바탕

클래스 Class : ES6에서 클래스가 도입되었다. 클래스 또한 함수이며, 기존 프로토타입 기반 패턴의 '문법적 설탕' 이라고 볼 수 있다. 클래스와 '생성자 함수'는 모두 프로토타입 기반의 인스턴스를 생성하지만 정확히 동일하게 동작하지는 않는다. 클래스는 생성자 함수보다 엄격하며 생성자 함수에서는 제공하지 않는 기능을 제공한다.

자바스크립트는 객체 기반의 프로그래밍 언어이며 JS를 이루고 있는 거의 모든 것이 객체이다. 원시 타입의 값을 제외한 나머지 값들( 함수, 배열, 정규 표현식 등)은 모두 객체이다.

19.1 객체지향 프로그래밍

객체지향 프로그래밍은 전통적인 명령형 프로그래밍의 절차지향적 관점에서 벗어나, 여러 개의 독립적 단위, 즉 객체의 집합으로 프로그래밍을 표현하려는 프로그래밍 패러타임이다.

객체지향 프로그래밍은 실체의 사물(객체)의 인식을 프로그래밍에 접목하려는 시도에서 시작되었다. '실체'는 특징이나 성질을 나타내는 [속성] attribute/ property을 가지고 있고, 이를 통해서 실체를 인식하거나 구별할 수 있다.

예를 들어서 실체가 사람이라고 한다면 수많은 속성이 존재하지만, 그 중 '이름'과 '나이'만 필요하다면 이것만을 포함한 객체를 만들 수 있다. 이를 [추상화]abstruction라 한다.

'속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조' 객체라 한다.

const circle = {
  radius: 5, // 반지름

  // 원의 지름: 2r
  getDiameter() {
    return 2 * this.radius;
  },

  // 원의 둘레: 2πr
  getPerimeter() {
    return 2 * Math.PI * this.radius;
  },

  // 원의 넓이: πrr
  getArea() {
    return Math.PI * this.radius ** 2;
  }
};

console.log(circle);
// {radius: 5, getDiameter: ƒ, getPerimeter: ƒ, getArea: ƒ}

console.log(circle.getDiameter());  // 10
console.log(circle.getPerimeter()); // 31.41592653589793
console.log(circle.getArea());      // 78.53981633974483

객체지향 프로그래밍은 객체의 상태state를 나타내는 데이터와, 상태 데이터를 조작하는 동작behavior을 하나의 논리적인 단위로 묶어서 인식한다. 따라서 객체상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조라고 할 수 있다.

19.2 상속과 프로토타입

[상속]inheritance은 객체지향 프로그래밍의 핵심 개념으로 어떤 객체의 프로퍼티 혹은 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것이다.

JS는 상속을 이용해서 중복을 제거하고, 코드를 재사용한다.

// 생성자 함수
function Circle(radius) {
  this.radius = radius;
  this.getArea = function () {
    // Math.PI는 원주율을 나타내는 상수다.
    return Math.PI * this.radius ** 2;
  };
}

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

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

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

'생성자 함수'로 동일한 프로퍼티 구조를 갖는 객체를 생성할 수 있으나, 위와같이 생성자 함수를 쓴다면 인스턴스 생성시마다 메서드가 중복생성, 중복 소유되는 문제점이 발생한다.

모든 인스턴스가 동일한 메서드를 중복 소유하는 것은 메서드를 불필요하게 낭비하며, 인스턴스 생성시마다 메서드를 생성하므로 퍼포먼스에도 악영향을 준다.

상속을 통해서 해당 중복을 해결할 수 있다.

// 생성자 함수
function Circle(radius) {
  this.radius = radius;
}

// Circle 생성자 함수가 생성한 모든 인스턴스가 getArea 메서드를
// 공유해서 사용할 수 있도록 프로토타입에 추가한다.
// 프로토타입은 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으로부터 getArea 메서드를 상속받는다.
// 즉, Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메서드를 공유한다.
console.log(circle1.getArea === circle2.getArea); // true

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

위에서 인스턴스는 자신의 프로토타입 객체, 즉 상위(부모) 객체 역할을 하는 Circle.prototype의 모든 프로퍼티와 메서드를 상속받는다.

이처럼 상속은 코드의 재사용이란 관점에서 매우 유용하다.

19.3 프로토타입 객체

프로토타입 객체는 객체지향 프로그래밍의 근본인 [상속]inheritance을 구현하기 위해 사용된다. [프로토타입] : 어떤 객체의 상위 객체의 역할을 하는 객체로서 다른 객체에 공유 프로퍼티(메서드 포함)를 제공한다.

모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지며, 이 내부 슬롯의 값은 프로토타입의 참조이다.

Last updated