Prototype의 생성 - 면접에서 공부한 개념을 다른 말로 물어볼 땐..

willy·2022년 7월 23일
3
post-thumbnail

첫 기술 면접을 보고 멘탈이 탈탈 털려버렸다. 왠만한 질문에 제대로 대답하지도 못하는 상태를 느꼈다. 비슷한 질문이더라도, 내가 아는 대로 답했어야했는데, 그냥 해당 부분에 대해선 잘 모르겠다고 넘긴 것이 계속 마음에 걸린다. 같은 질문을 다시 받았을 때, 같은 실수를 반복하지 않기 위해 짧게라도 정리해둘 필요가 있다.


Prototype?

JavaScript는 Class 대신 기존의 객체를 복사하여 새로운 객체를 생성합니다. 이를 프로토타입 방식이라 말합니다.
이러한 이유로 JavaScript는 흔히 프로토타입 기반 언어(prototype-based language)이라 부릅니다. 모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로써 프로토타입 객체(prototype object)를 가진다는 의미입니다.

JavaScript에서는 객체 인스턴스와 프로토타입 간에 연결이 구성되며 이 연결을 따라 프로토타입 체인을 타고 올라가며 속성과 메소드를 탐색합니다. 이 과정을 프로토타입을 상속받는 과정이라고 말합니다.

ES2015부터 class 문법을 지원하기 시작했지만 그냥 syntax sugar일 뿐, 자바스크립트는 여전히 prototype 기반 언어인 셈입니다.


상속과 Prototype

상속은 객체지향 프로그래밍의 핵심 개념으로, 어던 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것을 말합니다.

JavaScript는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복을 제거합니다. 중복을 제거하는 방법의 대표적인 예로는 기존의 코드를 재사용하는 것이죠.

function Circle(radius) {
  this.radius = radius;
  this.getArea = function () {
    return Math.PI * this.radius ** 2;
  };
}

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

console.log(circle1.getArea === circle2.getArea); // False

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

생성자 함수를 이용해서 동일한 프로퍼티를 갖는 객체를 여러개 만들 수 있습니다. 그러나 위 예제의 함수는 문제가 있습니다.

Circle 생성자가 만드는 객체는 모둔 동일한 프로퍼티와 메서드를 가집니다. 프로퍼티의 경우는 계속 달라지지만, getArea의 로직은 모두 같습니다. 그러니 메모리 효율을 위해 중복을 막기 위해서는 프로토타입을 이용하는 것이 좋습니다. 그 예로 엄격 비교 연산자를 통해 검사했을 때 False가 출력되는 것을 확인할 수 있습니다.

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

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

이렇게 프로토타입을 이용하게 된다면, getArea 메서드는 단 하나만 생성되어 프로토타입을 상속받게 됩니다. 비교 연산자를 적용했을 경우, true가 출력되는 것을 확인할 수 있습니다.

상속은 코드의 재사용이라는 관점에서 매우 유용합니다. 생성자 함수가 생성할 인스턴스가 공통적으로 사용할 프로퍼티나 메서드를 프로토타입에 미리 구현해둔다면, 인스턴스는 별도의 구현 없이 상위 객체인 프로토타입을 공유받아 사용할 수 있습니다.


Prototype 객체

프로토타입 객체는 객체간 상속을 구현하기 위해 사용됩니다. 프로토타입 객체의 부모 열할을 하는 객체로서 다른 객체에 공유 프로퍼티,메서드를 제공합니다.

모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지게 됩니다. 이 내부 슬롯값은 프로토타입의 참조가 됩니다. 여기에 저장는 프로토타입은 객체 생성 방식에 의해 결정됩니다. 즉, 생성될 때 객체 생성 방식에 따라 프로토타입이 결정되고 슬롯에 저장되는 것이죠.

이것만 기억합시다.

  • 모든 객체는 하나의 프로토타입을 갖습니다.
  • 모든 프로토타입 생성자 함수와 연결되어 있습니다.
  • 객체와 프로토타입과 생성자 함수는 서로 연결되어 있습니다.

그렇다면 객체의 프로토타입은 어떻게 접근할 수 있을까요?
__proto__을 이용하면 내부 슬롯 [[Prototype]]에 접근할 수 있습니다.

참고로, __proto__는 객체가 직접 소유하는 프로퍼티가 아닙니다. object.prototype.__proto__로 접근자 프로퍼티를 사용할 수 있습니다.

이때, __proto__을 쓰는 이유에 대해서 조금만 더 생각해보면, 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서도 있습니다. 이게 무슨 말인지 조금 헷갈릴 수도 있습니다. 이는 뱀이 자신의 꼬리를 삼키는 것과 비슷합니다.

이 그림은 우로보로스라고 합니다. 주로 영원성과 불멸성을 상징하는 그림으로 뱀이 자신의 꼬리를 삼키고 있는 모습입니다. 갑자기 이 얘기를 왜하냐구요?

만약 프로토타입을 서로를 가리키게 된다면, 비정상적입니다. 이럴때 에러를 발생시킵니다.

const parent = {};
const child = {};

child.__proto__ = parent;
parent.__proto__ = child; // TypeError: Cyclic __proto__ value

여기서 알 수 있는 것은 프로토타입은 순환하지 않습니다. 단방향으로 흘러야한다는 것입니다. 이렇게 순환참조하게 된다면, 체인의 종점이 없기 때문에, 프로토타입에서 프로퍼티를 검색할 때, 무한 루프에 빠지게 됩니다.

물론 이런 접근자 프로퍼티를 코드내에 사용하는 것은 권장되는 방법은 아닙니다. 만약, ES6에서 직접 참조를 만들어주고 싶다면, object.getPrototypeOf이라는 메서드를 사용할 것을 권장하고 있습니다.


Prototype의 생성 시점

프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성됩니다. 여기서 생성자 함수는 크게 두가지로 나눌 수 있습니다.

  1. 사용자 정의 생성자 함수
  2. 빌트인 생성자 함수

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

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

즉, new 연산자로 생성자 함수를 호출할 때, 객체의 프로토타입도 함께 생성됩니다.

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

Object, String, Number, Array, Function, Date, Promise ,RegExp 등과 같은 빌트인 함수도 일반 함수와 마찬가지로 생성되는 시점에 프로토타입이 생깁니다. 생성된 프로토타입은 빌트인 함수의 prototype 프로퍼티에 바인딩됩니다.


Prototype chain

이건 이전에 정리한 경험이 있습니다. 프로토타입 chain이란 개념은 JS에서 한 객체의 값을 찾을때, 프로토타입을 참조하며, 만약에 그렇게도 못찾을 경우엔 프로토타입의 프로토타입까지 찾아 내려가서 결국 마지막 프로토타입에 도달하는 것을 프로토타입 체인이라고 설명했습니다.

상속 관점에서 자바스크립트의 유일한 생성자는 객체뿐입니다. 각각의 객체는 프로토타입이라는 은닉 속성을 가지는데 자신의 프로토타입이 되는 다른 객체를 가리킵니다.

그 객체의 프로토타입 또한 프로토타입을 가지고 있고 이것이 반복되다, 결국 null을 프로토타입으로 가지는 오브젝트에서 끝납니다. null은 더 이상의 프로토타입이 없다고 정의되며, 프로토타입 체인의 종점 역할을 하는 셈입니다.

마치 러시아 인형마냥 계속해서 파고 들어가는 것과 비슷하다는 생각이 들었습니다.

prototype의 생성 시점


이것이 프로토타입의 생성 과정이다. 이전에 공부를 하면서 프로토타입의 체이닝에 대해서 알고 있었지만 이 것을 생성하는 과정이라고 받으니, 머리가 하얘졌다. 이렇게 개념을 연결할 생각을 전혀 하지 못했던 것이 너무 아쉽기도 했다.

다른 말로 하면 몰랐기 때문에 답을 못했던 것이다.

만약 기회가 한번 더 주어진다면, 내가 아는 개념을 조금이라도 이어서 답하는 능력을 탑재해야겠다. 나만의 언어에 갇히지 말고, 사람들과 대화하며 학습해야겠다.

profile
같은 문제에 헤매지 않기 위해 기록합니다.

0개의 댓글