JavaScript 프로토타입 알아보기

박효진 (@gywlsp)

프로토타입

자바스크립트는 프로토타입 기반 언어이다. 클래스 기반 언어에서는 ‘상속’을 사용하지만 프로토타입 기반 언어에서는 어떤 객체를 원형(prototype)으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻는다.

var instance = new Constructor();
프로토타입-도식

어떤 생성자 함수(Constructor)를 new 연산자와 함께 호출하면, Constructor에 정의된 내용을 바탕으로 새로운 인스턴스(instance)가 생성된다. 이때 instance에는 __proto__라는 프로퍼티가 자동으로 부여되는데, 이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조한다. 이는 문법 생성자(배열 리터럴, 객체 리터럴 등)로 객체를 생성할 때도 마찬가지다.

prototype은 객체이고, prototype 객체 내부에는 인스턴스가 사용할 프로퍼티나 메서드를 저장한다. __proto__는 생성자의 prototype 프로퍼티를 참조하기 때문에 인스턴스에서는 __proto__를 통해 이 메서드들에 접근할 수 있다.

예를 들어, Person이라는 생성자 함수의 prototypegetName이라는 메서드를 지정했다고 해보자.

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

Person.prototype.getName = function () {
  return this._name;
};

이때 Person의 인스턴스는 __proto__프로퍼티를 통해 getName을 호출할 수 있다. 왜냐하면 인스턴스의 __proto__가 Constructor의 prototype 프로퍼티를 참조하므로 결국 둘은 같은 객체를 바라보기 때문이다.

var suzi = new Person('수지');
suzi.__proto__.getName(); // undefined
Person.prototype === suzi.__proto__; // true

그러나 여기에서 this._name이 ‘수지’가 아니라 undefined로 출력되었다. 그 이유는 함수를 메서드 방식으로 호출했기 때문에, thissuzi가 아닌 suzi.__proto__(메서드명 바로 앞의 객체)가 되기 때문이다. 그리고 프로토타입 객체 내부에 name property를 추가한 것이 아니기 때문에 undefined가 출력된다.

this가 인스턴스가 되도록 하기 위해서는, __proto__ 없이 곧바로 메서드를 쓰면 된다. __proto__는 생략 가능한 프로퍼티이기 때문에, __proto__를 생략하더라도 Constructor의 prototype 내부의 메서드를 자신의 메서드인 것처럼 호출할 수 있다. 평소에 JavaScript를 사용했던 개발자라면 배열을 담은 변수의 length 프로퍼티를 이용하거나 concat, push, forEach 등의 메서드를 호출해본 경험이 있을 것이다. 이것은 앞서 말했듯 __proto__ 프로퍼티가 생략 가능하도록 설계되어있어 인스턴스가 표준 내장 객체의 메서드 및 속성을 자신의 것처럼 호출할 수 있었던 것이다.

프로토타입 체인

어떤 데이터의 __proto__ 내부에 다시 __proto__가 연쇄적으로 이어진 것을 프로토타입 체인이라고 하고, 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝이라고 한다. 프로토타입 체이닝을 통해 각 프로토타입 메서드를 자신의 것처럼 호출할 수 있고, 이때 접근 방식은 자신으로부터 가장 가까운 대상부터 점차 먼 대상으로 나아가며, 원하는 값을 찾으면 검색을 중단한다. 아래 예시에서 프로토타입 체인을 확인할 수 있다.

var a = [1, 2];
console.dir(a);
프로토타입-체인-예시

어떤 생성자 함수이든 prototype은 반드시 ‘객체’이기 때문에 Object.prototype이 언제나 프로토타입 체인의 최상단에 존재한다. 그래서 Object.prototype에는 모든 데이터 타입에서 사용할 수 있는 범용적인 메서드만이 존재하며, 객체 전용 메서드는 여느 데이터 타입과 달리 Object 생성자 함수에 static하게 담겨있다. 객체에서만 사용할 메서드를 Object.prototype 내부에 정의한다면, 다른 데이터 타입도 해당 메서드를 사용할 수 있게 되기 때문이다.

참고 자료