프로토타입이란?

개발 log·2021년 10월 4일
0

JS 지식

목록 보기
20/36
post-thumbnail

프로토타입의 개념에 대해 공부를 하고 어디서 활용되는지 잘 알지 못했는데 공부를 진행하며 생성자 함수와 DOM의 상속과정에서 프로토타입이 다시 언급되어 DOM 조작을 예시로 프로토타입을 정리해보려 한다.



프로토타입이란?

먼저 프로토타입이란 무엇일까?
프로토타입을 이해하려면 아래의 두 문항만 알면된다.

  1. 어떤 객체의 상위 객체역할을 하는 객체
  2. JS에서 상속을 구현하는 메커니즘

이 두 문항을 이해하려면 우선 상속이라는 개념에 대해 짚고 넘어갈 필요가 있다.

상속이란?

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

그렇다면 왜 상속을 받아야하고 상속은 무엇이 좋을까?

모든 인스턴스가 메서드를 갖는 경우

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

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

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

위와 같은 코드가 있다면 Circle생성자 함수는 인스턴스를 생성할 때 마다 동일한 동작을 하는 getArea메서드를 중복 생성하고 같은 동작을 하는 메서드를 모든 인스턴스가 중복하여 소유하고 있는 것이 된다.

그렇다면 같은 동작을 하는 메서드가 중복해서 선언될 필요가 있을까?
특정한 공간에 저장해두고 그 메서드를 가져다 쓰는 방식이면 필요없는 메모리 낭비를 줄일 수 있지 않을까?

이러한 고민을 해결해주는 것이 바로 상속인 것이다.

JS는 이러한 상속을 프로토타입을 기반으로 구현한다.

상속을 통해 메서드를 공유하는 경우

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

// 프로토타입에 getArea 메서드를 추가
Circle.prototype.getArea = function() {
  return Math.PI * this.radius ** 2;
};

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

위 그림처럼 프로토타입을 사용하면 Circle 생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입, 즉 상위객체 역할을 하는 Circle.prototype의 모든 프로퍼티와 메서드를 상속받는다.

이렇게 코드를 구현하면 코드의 재사용적 관점에서 매우 유용하다.

생성자 함수가 생성할 모든 인스턴스가 공통적으로 사용할 프로퍼티나 메서드를 프로토타입에서 관리하기 때문에 인스턴스는 별도의 구현없이 상위 객체인 프로토타입의 자산을 공유하여 사용할 수 있기 때문이다.

프로토타입 내부슬롯 [[Prototype]]

조금 더 프로토타입에 대해 깊게 알아보자

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

[[Prototype]]에 저장되는 프로토타입은 객체 생성 방식에 의해 결정되는데 즉 객체가 생성될 때 객체 생성 방식에 따라 프로토타입이 결정되고 [[Prototype]] 내부슬롯에 저장되는 것이다.

모든 객체는 하나의 프로토타입을 가지며 모든 프로토타입은 생성자 함수와 연결되어 있다.

[[Prototype]]내부슬롯에는 직접 접근할 수 없지만 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입에 간접적으로 접근할 수 있다.

그리고 프로토타입은 자신의 constructor프로퍼티를 통해 생성자 함수에 접근할 수 있고 생성자 함수는 자신의 prototype프로퍼티를 통해 프로토타입에 접근할 수 있다.

여기까지는 앞으로 설명할 프로토타입에 대해 기본 지식만 정리한 것이다.

이후로는 예제를 통해 알아보자.



생성자함수에서 프로토타입

// 생성자함수
function Person(name) {
  this.name = name;
}

const me = new Person('Son');

위와 같이 Person생성자 함수로 me인스턴스를 만드는 경우 프로토타입은 어떻게 생성되며 관리되고 있을까?

그림을 통해 알아보면 다음과 같다.

생성자함수와 함께 프로토타입 객체 생성

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

그러므로 Person생성자함수가 생성되는 시점에 Person.prototype객체도 같이 생성된다.

하지만 이 생성과정은 단순히 Person생성자함수가 생성되서 위의 그림처럼 떠있는 것이 아닌 Person생성자 함수 역시 프로토타입 기반으로 생성되어 있다.

me 인스턴스 생성

me 인스턴스를 생성하면 me 객체의 프로토타입의 constructor프로퍼티를 통해 생성자 함수와 연결된다.

따라서 me 인스턴스는 프로토타입인 Person.prototypeconstructor프로퍼티 뿐 아니라 Person.prototype이 가진 프로퍼티와 메서드를 상속받아 사용할 수 있다.

이처럼 객체는 프로토타입을 통해 프로퍼티를 참조하고 메서드를 호출할 수 있는데 이러한 체이닝을 프로토타입 체인이라고 한다.

프로토타입 체인

프로토타입 체인을 제대로 알아보기 위해 아래와 같은 코드를 실행시켜보자

me.hasOwnProperty('name'); // true

분명 위에서 생성했던 생성자 함수 Person에는 hasOwnProperty라는 메서드가 없음에도 불구하고 hasOwnProperty라는 메서드가 잘 동작하는 것을 알 수 있다.

그렇다면 hasOwnProperty는 어디에 존재하고 있는 것일까?

아래 그림을 보면 Object.prototype객체에 hasOwnProperty가 있는 것을 볼 수 있다.

하지만 분명 Object.prototype은 우리가 생성한 적 없는 객체인데 프로토타입체인에 존재하고 있는 이유는 무엇일까?

그 이유는 JS는 기본적으로 코드를 평가하기 이전, 즉 런타임 이전에 표준 빌트인 객체가 포함되어 있는 전역 객체를 생성하는데 표준 빌트인 객체에서 Object생성자 함수를 제공하며 Object생성자 함수 역시 생성됨과 동시에 Object.prototype객체를 생성하며 모든 객체는 Object생성자 함수를 이용하여 만드는 것이기 때문에 Person.prototype객체 역시 Object.prototype객체를 상속받아 Object.prototype까지 포함된 프로토타입 체인을 형성하는 것이다.



DOM에서의 프로토타입

이 글에서 DOM이 정확히 무엇인지는 다루지 않을 것이지만 내용의 이해를 돕기위해 간단하게 정리하고 넘어가려 한다.

먼저 DOM이란 노드 객체들로 구성된 트리 자료구조이다.
즉 DOM 역시 객체들로 구성이 이루어져있다는 말이다.

그렇다면 DOM은 어떤식으로 상속이 이뤄지고 있을까?

input요소 노드의 어트리뷰트 노드를 수정한다고 가정해보자

<input id="user" type="text" value="wonjae">
const $input = document.querySelector('input');

$input.setAttribute('value', 'foo');

input요소 노드가 생성되면 당연히 어트리뷰트 노드에 대한 연결정보와 자식노드에 대한 정보들을 갖고 있을 것이다.

하지만 setAttribute라는 메서드는 갖고 있지 않을 것이다.

이를 어떻게 탐색해서 메서드를 사용할 수 있게 하는 것일까?

탐색과정은 아래 그림과 같다.

  1. 스코프체인을 통해 식별자가 유효한지 탐색한다.

  2. 식별자가 유효하다면 객체인지 확인한 후 프로토타입 체인에서 setAttribute 프로퍼티가 존재하는지 확인한다.

  3. input요소 노드 객체에 해당 프로퍼티가 없으므로 프로토타입 체인을 통해 해당 프로퍼티가 존재하는 Element.prototype객체까지 이동하여 탐색한다.

  4. 인수를 넘겨받아 메서드를 호출한다.

위와 같은 과정을 거쳐 식별자는 스코프체인에서 메서드는 프로토타입 체인에서 검색하여 input요소 노드 객체의 어트리뷰트 노드를 조작할 수 있는 것이다.

profile
프론트엔드 개발자

0개의 댓글