티스토리 뷰

프로토타입이란 자신의 상위 객체(또는 부모 객체) 역할을 하는 객체로서, 하위(자식) 객체에 프로퍼티와 메서드를 상속한다.

즉 객체는 프로토타입으로부터 메서드를 포함한 프로퍼티들을 상속받는다.

이 상속을 통해 불필요한 중복을 제거할 수 있는데, 예시를 들어보자.

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

// 생성자 함수로 인스턴스 생성
var circle1 = new Circle(1);
var circle2 = new Circle(2);

circle1.getArea === circle2.getArea; // false

circle1.hasOwnProperty('getArea') // true
circle2.hasOwnProperty('getArea') // true

Circle 생성자 함수로 생성된 모든 인스턴스들은 동일한 프로퍼티를 갖게 된다. 문제는 getArea 메서드가 중복해서 생성 및 소유된다는 점이다. 모든 인스턴스들이 getArea 메서드를 갖게 되므로 불필요한 중복과 메모리 낭비가 생긴다.

각 인스턴스마다 메서드를 생성하고 소유하므로 circle1과 circle2의 getArea를 비교해보면 다른 것을 알 수 있다. 또한 getArea가 각 인스턴스들에게 직접 속한 고유 프로퍼티라는 것을 알 수 있다.

radius 프로퍼티는 각 인스턴스마다 다를 수 있는 값이므로 각자 별도로 갖고 있는 것이 맞겠지만, getArea는 인스턴스마다 별도로 갖고 있을 이유가 없는 함수이므로 하나만 생성해서 모든 인스턴스가 공유하는 것이 바람직하다. 이 때, 프로토타입을 이용하면 된다.

// 컨스트럭터(생성자 함수)
function Circle(radius) {
  this.radius = radius
}

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

// 생성자 함수로 인스턴스 생성
// 인스턴스들은 자신의 프로토타입, 즉 Circle.protoype에 정의된 프로퍼티와 메서드를 상속받는다
var circle1 = new Circle(1);
var circle2 = new Circle(2);

circle1.getArea === circle2.getArea; // true

circle1.hasOwnProperty('getArea'); // false
circle2.hasOwnProperty('getArea'); // false

Circle.prototype.hasOwnProperty('getArea'); // true

생성자함수에서 radius는 동일하게 놔두고, getArea만 따로 프로토타입의 프로퍼티로 설정하였다.

Circle.prototype 으로 생성자 함수 프로토타입에 접근할 수 있다. 자바스크립트에서는 함수 또한 객체로 취급되므로 메서드와 프로퍼티를 가질 수 있는데, 함수가 갖는 프로퍼티가 바로 prototype이라는 프로퍼티다. 자식 객체는 생성자 함수 프로토타입에 정의된 프로퍼티와 메서드를 모두 상속받으므로, 여기에 getArea를 정의한 것이다. 이렇게 하면 circle1과 circle2는 프로토타입(Circle.prototype)의 메서드를 공유한다. (참고로, ES6의 클래스 문법을 사용하면 그 안에 정의한 메서드는 자동으로 프로토타입 메서드가 된다. 단 정적 메서드가 아닐 경우.)

proto

모든 객체는 __proto__ 접근자를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있다. __proto__ 를 통해 접근하거나 할당하면 내부적으로는 getter와 setter 함수를 호출한다.

이 접근자는 각 객체들이 고유하게 갖고 있는 프로퍼티가 아니라 최상위 객체인 Object.prototype 의 프로퍼티이고, 상속받아서 사용하는 것이다.

이렇게 접근자를 통해 간접적으로 접근하는 이유는 순환 참조를 막기 위해서다.

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

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

자바스크립트 엔진이 어떤 객체의 프로퍼티를 찾으려고 할 때, 처음에는 그 객체에서 찾고 없으면 그 객체의 프로토타입, 또 없으면 프로토타입의 프로토타입 ... 이렇게 올라가면서 순차적으로 찾고 최상위 프로토타입까지 갔는데도 없으면 undefined를 리턴한다. 이것을 프로토타입 체인이라고 한다. 만약 순환 참조가 가능하다면 프로토타입에서 프로퍼티를 찾을 때 무한 루프에 빠지게 되므로 이를 방지하기 위해서 무조건적으로 프로토타입을 할당하게 하지 않고 __proto__ 라는 접근자를 사용하는 것이다.

prototype

함수 객체가 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다. 따라서 non-constructor인 화살표 함수와 ES6 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않는다. (함수 선언식, 표현식으로 정의된 일반 함수만 constructor이다)

Circle.prototype === circle1.__proto__ // true
Circle.prototype === circle2.__proto__ // true

모든 일반 함수는 prototype 프로퍼티를 갖지만 생성자 함수가 아닌 함수의 prototype 프로퍼티는 아무런 의미가 없다.

constructor

모든 프로토타입은 constructor 프로퍼티를 갖고 있다.

생성된 인스턴스는 프로토타입의 constructor 프로퍼티로 자신의 생성자 함수와 연결된다.

circle1.constructor === Circle // true
circle1.__proto__.constructor === Circle // true

예제에서 circle1은 프로토타입의 프로퍼티를 모두 상속받으므로 constructor 또한 상속받아 사용할 수 있다. 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 pair로 존재한다.

Ref

위키북스 - 모던 자바스크립트 Deep Dive

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함