본문 바로가기
공부/JS

[Javascript] 프로토타입 알아보기 - 1

by Piva 2024. 1. 24.
  • 코어 자바스크립트를 읽고, 프로토타입에 대해 공부한 내용을 정리한다.

프로토타입(Prototype)

: 자바스크립트의 모든 객체와 연결된, 부모 역할을 담당하는 객체.

  • JS에는 클래스 기반이 아닌, 프로토타입 기반 객체 지향 언어이다.
  • 프로토타입을 통해 객체 지향의 상속 개념을 구현한다.

 

프로토타입의 개념

  다음과 같은 코드가 있을 때, 이 코드를 실행한 결과를 도식화 하면 아래와 같은 그림을 그릴 수 있다.

 

const instance = new Foo();

 

프로토타입을 도식화한 그림

  • Foo 생성자 함수를 통해 Foo의 인스턴스인 instance를 생성하고 있다.
  • 이 때, instance의 __proto__([[prototype]]) 프로퍼티가 자동으로 부여되고, 이 프로퍼티는 해당 인스턴스의 부모의 프로퍼티(여기서는 Foo의 prototype)를 가리킨다.

 

__proto__ 와 [[prototype]]

- ES5 명세에는 __proto__가 아닌, [[prototype]]으로 정의되어있음.
- 본래 __proto__는 브라우저가 [[prototype]]을 구현한 것.
- 프로토타입에 접근하기 위해서는 instance.__proto__를 사용할 수 있으나, 이는 권장되지 않고 Object.getPrototypeOf() 등을 활용하는 것이 권장됨.

 


__proto__ & prototype

  둘 다 프로토타입 객체를 가리키지만, 같은 것은 아니다.

  • __proto__ 는 객체 입장에서 자신의 부모 역할을 하는 프로토타입 객체를 가리킨다.
  • prototype 객체는 함수만 갖는다(즉, 위의 instance는 prototype을 갖고 있지 않다!).
    • 이 함수가 생성자로 사용될 때, 이 함수로 생성된 객체의 __proto__가 가리키는 대상이 된다.
    • 이 함수로 만들어진 인스턴스가 사용할 수 있는 메서드가 저장되어 있다.

 

  prototype에 지정한 메서드는 해당 함수로 만들어진 인스턴스들이 접근할 수 있게 된다.

 

class Foo {
    constructor (bar) {
        this.bar = bar;
    }
};

Foo.prototype.getBar = function () {
	return this.bar;
};

const instance = new Foo('Hello');
console.log(instance.getBar()); // instance의 bar에 저장된 Hello가 출력된다.

 

  • 위에서 instance가 getBar 함수를 호출할 수 있는 이유는, instance의 __proto__가 Foo의 prototype을 참조하기 때문이다.
  • 하지만, getBar 함수를 호출할 때 __proto__를 사용하지 않고도 호출할 수 있음을 알 수 있다.
  • 이는 JS에서 __proto__가 생략 가능한 프로퍼티이기 때문이다!
  • 따라서 인스턴스는, 자신의 생성자 함수의 prototype에 지정된 프로퍼티나 메서드를 자신의 것처럼 접근할 수 있다.

 

const arr = new Array(3);
console.dir(arr);
console.dir(Array);

 

  위의 코드를 실행하면 아래와 같은 결과를 얻을 수 있다.

console.dir(arr) 의 결과

 

console.dir(Array) 의 결과

  • Array 생성자로 생성한 배열 arr의 [[Prototype]] (__proto__)에 생성자 함수인 Array가 들어가 있다.
  • 생성자 함수 Array를 출력한 결과를 보면, prototype 프로퍼티 내부에 push, shift 등의 함수가 들어가 있다. 이는 arr의 __proto__와 동일한 내용이다.
  • 따라서 생성자 함수로 만들어진 인스턴스는, 자신의 __proto__를 통해 생성자 함수의 prototype을 참조할 수 있으며, 이를 호출할 때는 __proto__를 생략할 수 있으므로 인스턴스의 메서드인 것 처럼 사용할 수 있다.
  • 그러나 생성자 함수의 prototype에 포함되어 있지 않은 메서드는 다른 메서드는 인스턴스가 호출할 수 없다.
     → ex) Array 생성자의 isArray() 등의 메서드는 arr.isArray() 같은 형태로 사용이 불가능하다.

MDN의 Array의 메서드 목록. 이제 prototype의 유무에 따른 차이를 알 수 있다!

 

 

Constructor 프로퍼티

: __proto__와 prototype 두 객체의 내부에 존재하는 프로퍼티. 원래의 생성자 함수(자기 자신)를 참조한다. 

  •  인스턴스의 원형을 알 수 있는 수단으로 기능한다.
  • constructor가 가리키는 대상은 변경이 가능하다.
    • 다만, 변경한다고 해서 해당 인스턴스의 원형이나 타입이 변하지는 않는다(아래 코드 참조).
class Foo {
    constructor (bar) {
        this.bar = bar;
    }
};

const NewFoo = function () {
    console.log('New foo');
};

Foo.prototype.getBar = function () {
	return this.bar;
};

var instance = new Foo('Hello');
instance.constructor = NewFoo;

console.log(instance instanceof NewFoo); // false
console.log(instance.constructor.name); // NewFoo

 


  • 처음 프로토타입 개념을 공부하기 시작했을 때, 이해가 힘들어서 한참 허우적댔다(...) __proto__와 prototype의 차이를 깨닫는데 특히 시간이 걸렸던.
  • 다음 글에서는 프로토타입 체인에 대해 공부할 예정.