[JS] 프로토타입 이해하기 - 프로토타입은 나의 부모

초코침·2023년 3월 16일
5

JavaScript

목록 보기
16/26

자바스크립트는 프로토타입 기반 객체지향 프로그래밍 언어다.

다른 객체지향 프로그래밍 언어는 클래스를 기반으로 동작하지만 자바스크립트는 프로토타입을 기반으로 동작한다. 프로토타입은 객체지향 프로그래밍의 중요한 개념 중 하나인 상속과 밀접한 관련이 있다.

자바스크립트가 상속을 구현하는 방법

다음과 같이 Animal이라는 생성자 함수로 두 종류의 동물 객체를 만들어보자.

function Animal(species, sound) {
  this.species = species;
  this.sound = sound;

  this.getAnimalSound = function () {
    return this.sound;
  };
}

const dog = new Animal('dog', 'woof');
const pig = new Animal('pig', 'oink');

여기서 dog와 pig의 프로퍼티 중 speciessound만 다를 뿐, getAnimalSound라는 메서드는 동일한 역할을 한다.


그렇다면 dog의 getAnimalSound 메서드pig의 getAnimalSound 메서드는 같을까, 다를까?

console.log(dog.getAnimalSound === pig.getAnimalSound); // ??

정답은 “다름”이다. 완전히 동일한 역할을 할지라도, 두 객체는 메서드를 각각 소유하고 있다.

이때, 객체를 생성할 때마다 완전히 동일한 역할을 하는 메서드를 계속 생성할 필요가 있나? 라는 의문이 든다.

객체가 많이 만들어진다면, 그 만큼 동일한 메서드도 많이 만들어진다는 것인데 메모리도 아낄 겸 하나만 만들어 놓고 다 같이 공유할 순 없는걸까?


바로 여기서 상속의 개념이 등장한다. 타 클래스 기반 객체지향 언어에서는 공유할 프로퍼티나 메서드를 클래스에 넣어두고 상속을 받아 공유하게 된다.

하지만 자바스크립트는 클래스 기반 언어가 아니다. 대신, 프로토타입이라는 개념이 있다. 위와 같은 상황에서 클래스 역할을 하는 개념이 바로 프로토타입이다.

자바스크립트는 프로토타입을 기반으로 상속을 구현한다.

프로토타입 객체

프로토타입어떤 객체의 부모 객체 역할을 하는 객체로서 다른 객체에 공유 프로퍼티와 메서드를 제공한다. 부모 객체 즉, 프로토타입을 상속받은 자식 객체는 부모 객체의 프로퍼티를 자신의 프로퍼티처럼 자유롭게 사용할 수 있게 된다.

프로토타입은 부모 객체 역할이며, 프로토타입을 상속받은 자식 객체는 부모 객체의 프로퍼티와 메서드를 자신의 것처럼 쓸 수 있다.


그럼 위 예제의 getAnimalSound를 (Animal 생성자 함수를 통해 생성될)여러 객체들이 공유하기 위해서는 부모 객체 역할을 하게 될 Animal에 getAnimalSound를 정의해주면 될 것 같다. 이때 Animal이 자식 객체에게 물려주게 될 Animal 프로토타입은 어떻게 확인할 수 있을까?

prototype 프로퍼티

prototype 프로퍼티함수 객체만이 소유하는 프로퍼티이며, 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다. 즉, 생성자 함수라는 객체의 속성 중 하나인 prototype의 값이 바로 생성자 함수를 통해 생성될 객체의 부모 역할을 하게 되는 것이다.

(생성자) 함수 객체의 prototype 프로퍼티에는 자신이 생성할 객체에게 상속해 줄 프로토타입 즉, 자기 자신이라는 프로토타입 참조가 들어있다.


prototype 프로퍼티는 생성자 함수 역할을 하는 함수에만 있는 프로퍼티가 아니라 모든 함수가 가지고 있는 프로퍼티다. 단, 생성자 함수로서 호출할 수 없는 함수(화살표 함수, ES6 메서드 축약 표현으로 정의한 메서드)는 prototype 프로퍼티를 소유하지 않는다.

prototype 프로퍼티는 화살표 함수와 ES6 메서드 축약 표현으로 정의한 메서드를 제외한 모든 함수가 가지고 있는 프로퍼티다.


위에서 생성자 함수로서 선언한 Animal을 다시 살펴보면, 역시나 prototype 프로퍼티를 가지고 있음을 알 수 있다.


그리고 두 숫자를 더해주는 함수 addTwoNumbers가 있다. 이 함수는 객체를 생성하는 함수라기 보다는, 그저 두 수의 합을 반환하는 기능을 하는 함수다.

function addTwoNumbers(num1, num2) {
  return num1 + num2;
}

하지만, new와 함께 호출하면 생성자로서 역할을 할 수 있는 함수기 때문에 addTwoNumbers 함수 역시 prototype이라는 프로퍼티를 가지고 있다.

그러나 prototype 프로퍼티자신이 생성할 객체에 프로토타입을 할당하는 데 쓰이는 프로퍼티이므로 생성자 함수로서 사용되지 않을 함수에게는 의미가 없는 프로퍼티다.


반면, 함수가 아닌 일반 객체화살표 함수(+ES6 메서드)prototype 프로퍼티를 가지지 않는다고 했다.

객체가 프로퍼티를 소유하는지 여부는 hasOwnProperty 메서드로 확인할 수 있다.


그렇다면 다시 Animal로 돌아가서, Animal의 getAnimalSound 메서드를 자식 객체에게 물려줄 수 있도록 코드를 변경해 보자. 자식 객체의 부모 역할을 하게 될 프로토타입(부모 객체)은 생성자 함수의 prototype 프로퍼티이므로 Animal.prototypegetAnimalSound 메서드를 넣어주면 된다.

function Animal(species, sound) {
  this.species = species;
  this.sound = sound;

  Animal.prototype.getAnimalSound = function () {
    return this.sound;
  };
}

이렇게 작성한 다음, Animal 생성자 함수로 생성한 객체끼리 메서드를 비교해보면 같음(true)을 리턴한다.


결과적으로 dog와 pig의 부모 객체Animal이다. 즉, dog와 pig의 프로토타입Animal이 된다.

그럼 dog와 pig의 부모가 무엇인지(프로토타입이 무엇인지)는 어떻게 확인할 수 있을까?

프로토타입에 접근하기

모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지고, 이 내부 슬롯에 프로토타입의 참조 값이 들어있다. 즉, 자신의 부모 객체(프로토타입)가 무엇인지 알기 위해서는 이 [[Prototype]] 슬롯에 접근해야 한다.

하지만, 내부 슬롯은 프로퍼티가 아니기 때문에 [[Prototype]] 슬롯에 직접 접근할 수 없고, (ES6부터 표준으로 채택된) 접근자 프로퍼티 __proto__를 통해 접근할 수 있다.

나의 부모 객체가 무엇인지 알기 위해서는 즉, 프로토타입에 접근하기 위해선 접근자 프로퍼티인 __proto__를 이용해야 한다.


그럼 __proto__ 접근자 프로퍼티로 dog와 pig의 부모 객체가 무엇인지 확인해 보자.

dog와 pig의 부모 객체를 확인하기 위해 __proto__ 접근자 프로퍼티로 조회해 봤더니, Animal.prototype에 있던 constructor와 자식에게 물려주기 위해 직접 추가해줬던 getAnimalSound 메서드가 있는 객체 즉, Animal(정확히 말하면 Animal.prototype이라는 객체)dog와 pig의 부모 객체임을 확인할 수 있다.

Animal이 자식에게 물려주려던 Animal.prototypedog.__proto__와 같다.

즉, dog와 pig는 Animal을 상속받았다.


그렇다면 Animal의 부모 객체는 무엇이 될까?

프로토타입의 참조를 얻을 수 있는 getPrototypeOf 메서드로 Animal의 부모 객체를 조회해봤더니 함수가 나왔다.

[참고] 모든 객체가 proto 접근자 프로퍼티를 사용할 수 있는 것이 아니기 때문에, 코드 내에서 proto 접근자 프로퍼티의 사용은 권장되지 않는다. 프로토타입의 참조를 얻고 싶다면 getPrototypeOf 메서드를 쓰는 것을 권장한다고 한다.


Animal의 부모 객체(Animal._ _proto __)와 함수 객체가 물려줄 함수라는 프로토타입(Function.prototype)을 비교해보면 동일하다는 것을 알 수 있다.

즉, 생성자 함수인 Animal의 부모 객체는 함수 객체(Function.prototype)인 것이다.

정리

지금까지 살펴본 생성자 함수와 인스턴스의 관계를 그림으로 표현하면 다음과 같다.

그리고 프로토타입이라는 용어가 많이 헷갈리는데 일단 프로토타입이란 나(객체)의 부모다.

좀 더 정리하자면,

생성자 함수 입장

prototype 프로퍼티

생성자 함수가 생성할 자식의 부모 = 나!

  • 생성자 함수(나)가 만들 인스턴스(자식)에게 물려줄 것 (’나’라는 프로토타입)
  • 인스턴스(나의 자식)의 부모 객체(나)
  • 생성자 함수(나)로부터 생성된 인스턴스(자식)의 프로토타입(자식의 부모=나)

프로토타입

생성자 함수의 부모

  • proto 접근자 또는 Object.getPrototypeOf 메서드로 접근 가능
  • 생성자 함수(나)의 부모
  • 함수의 원형은 Function이라는 객체
  • 결국 생성자 함수의 프로토타입은 Function.prototype

일반 객체 입장

자바스크립트의 거의 모든 것은 객체다.

일반 객체는 생성자 함수가 아니기 때문에 물려줄 것(prototype 프로퍼티)이 없다. 따라서 자신의 부모(프로토타입)가 무엇인지만 알고있다.

프로토타입

객체의 부모

  • proto 접근자 또는 Object.getPrototypeOf 메서드로 접근 가능
  • 나(객체)의 부모(프로토타입)
  • 객체(나)의 프로토타입(부모)은 생성자 함수/Object/Array/Function 등

이제 프로토타입 체인을 이해하러 가 봅시다 ㅋㅋ

profile
블로그 이사중 🚚 (https://sungjihyun.vercel.app)

0개의 댓글