[JavaScript] ProtoType과 Constructor

Joowon Jang·2024년 7월 4일

JavaScript

목록 보기
6/17

JavaScript와 ProtoType

JavaScript는 프로토타입 기반 언어로, 여러 클래스 기반 언어와는 다르게 동작한다.
JavaScript에서는 클래스 기반 언어의 '상속'과 비슷한 효과를 내기 위해 어떤 객체를 원형으로 삼고 이를 복제(참조)하여 새로운 객체를 생성한다.

ProtoType이란?

'prototype'을 직역하면 '원형, 견본'이라는 뜻이다.
그리고 보통 '시제품이 나오기 전의 제품 디자인의 원형'이라는 의미로도 많이 사용한다.
JavaScript에서도 '어떤 객체를 양산하기 위한 원형'정도로 생각하는게 적당할 것 같다.


ProtoType과 Constructor

JavaScript에서는 함수에 new 키워드를 붙여 호출할 수 있다.

(이 예시를 아래에서도 계속 사용하여 정리하겠음)

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

const joowon = new Person('주원');

이렇게 new 키워드를 붙여서 호출하는 함수를 생성자(Constructor)라고 하며, 이 생성자를 통해 생성된 객체를 인스턴스(instance)라고 한다.

생성된 인스턴스를 console.log로 열어보면 아래와 같이 [[Prototype]]이라는 프로퍼티가 있고, 그 안의 constructor라는 프로퍼티자신을 생성한 생성자 함수(Constructor)를 참조하고 있는 것을 확인할 수 있다.

또, 생성자 함수를 console.dir 메서드로 열어보면, 아래와 같이 prototype이라는 속성이 있고,
prototype 프로퍼티는 해당 생성자 함수를 통해 만들어진 인스턴스의 [[Prototype]] 프로퍼티와 동일한 것을 볼 수 있다.

✔️ __proto__ (던더프로토)
[[Prototype]]__proto__로도 사용하는데,
ES5.1(및 그 이후 버전) 명세에는 [[Prototype]]이라는 명칭으로 정의되어 있다.
__proto__라는 프로퍼티는 사실 브라우저들이 [[Prototype]]에 직접 접근하기 위해 구현한 기능에 불과하다. (getter 및 setter의 역할을 함)
명세에서는 instance.__proto__와 같은 방식으로 직접 접근하는 것을 허용하지 않았지만, 대부분의 브라우저들이 __proto__로 직접 접근하는 방식을 포기하지 않았고, ES6에서는 호환성 유지 차원에서 정식으로 인정되었다.
하지만, 권장되는 방식이 아니며, 브라우저가 아닌 다른 환경 또는 일부 브라우저에서는 지원되지 않을 가능성이 있기 때문에 Object.getPrototypeOf(), Object.create() 등을 사용하자.


Constructor.prototype === instance.__proto__

아래 코드의 결과처럼 생성자 함수의 prototype 프로퍼티와 인스턴스의 [[Prototype]] 프로퍼티는 동일하다.

console.log(Person.prototype === joowon.__proto__); // true
/* __proto__는 권장되는 방식이 아니므로 아래와 같이 사용하자 */
console.log(Person.prototype === Object.getPrototypeOf(joowon)); // true

+ 생성자 함수의 prototypeconstructor 프로퍼티는 자신을 참조하고 있기 때문에 아래와 같은 결과도 볼 수 있다.

console.log(Person.prototype.constructor === Person) // true

생략 가능한 프로퍼티 __proto__와 메서드

생성자 함수의 prototype 프로퍼티는 객체이므로
아래와 같이 prototype 프로퍼티의 메서드를 추가할 수 있다.

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

Person.prototype.func1 = function() {
    console.log(this._name);
}

const joowon = new Person("주원");

(생성자 함수 Person의 prototype 프로퍼티의 메서드가 인스턴스인 joowon의 [[Prototype]] 프로퍼티에도 존재함)

Person 함수는 prototype 프로퍼티의 메서드인 func1을 호출할 수 있다.

Person.prototype.func1(); // undefined

그렇다면 인스턴스는 아래와 같이 [[Prototype]]의 메서드를 사용할 수 있을 것이다.

joowon.__proto__.func1(); // undefined

여기에는 문제가 하나 있다.
joowon이라는 인스턴스의 _name 값은 "주원"이지만, 여기서 func1 메서드를 호출하는 (this)주체는 joowon이 아니라 joowon의 [[Prototype]] 프로퍼티이다.
해당 [[Prototype]] 프로퍼티(객체)에는 _name이라는 값이 없기 때문에 undefined가 출력되는 것이다.

그런데, 사실 __proto__는 생략 가능한 프로퍼티이다.
그래서 아래와 같이 사용할 수 있다.

joowon.func1(); // "주원"

이렇게 사용하면 원하는 결과인, joowon 객체의 _name 값("주원")이 출력된다.
즉, 생성자 함수의 prototype 프로퍼티에 어떤 메서드(또는 프로퍼티)가 있으면, 그 생성자 함수로부터 만들어진 인스턴스에서도 자신의 메서드(또는 프로퍼티)처럼 사용할 수 있다.

"왜 이렇게 되는지"는, '생략 가능한 프로퍼티'라는 개념을 JavaScript의 창시자인 브랜든 아이크가 만든 것이기 때문에,
"왜 이렇게 되는지"가 아니라 "왜 이렇게 만들었는지"라고 묻는 게 맞는 것 같다...

다음에는 자바스크립트의 내장 생성자 함수(Array, Object 등)과 Prototype 및 Class에 대해 정리하여 포스팅할 예정!

profile
깊이 공부하는 웹개발자

0개의 댓글