자바스크립트는 프로토타입 기반 언어입니다.
클래스 기반 언어에서는 상속을 사용하지만 프로토타입 기반 언어에서는 어떤 객체를 원형(prototype)으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻습니다.
프로토타입은 하나의 객체입니다.
자바스크립트는 함수에 자동으로 객체인 prototype 프로퍼티를 생성하는데, 이 함수를 생성자 함수로서 사용할 경우(new 연산자와 함께 호출할 경우), 그로부터 생성된 인스턴스에는 숨겨진 프로퍼티인__proto__
가 자동으로 생성되며, 이 프로퍼티는 생성자 함수의 prototype 프로퍼티를 참조합니다.
예를 들어, Person이라는 생성자 함수의 prototype에 getName이라는 메서드를 지정했다고 해보죠.
const Person = function(name) {
this.name = name;
};
Person.prototype.getName = function() {
return this.name;
};
이제 Person의 인스턴스는 __proto__
프로퍼티를 통해 getName을 호출할 수 있습니다. 왜냐하면 인스턴스의 __proto__
는 생성자 함수의 prototype 프로퍼티를 참조하므로 결국 둘은 같은 객체를 바라보기 때문이죠.
const jaewon = new Person("jaewon");
jaewon.getName(); // "jaewon"
위에서 __proto__ 프로퍼티를 통해 getName을 호출할 수 있습니다.
라고 했는데, jaewon.__proto__.getName();
라고 호출하지 않아도 정상적으로 작동하는 이유는 __proto__
가 생략 가능한 프로퍼티이기 때문입니다.
개발자 도구의 콘솔로 좀 더 살펴보겠습니다.
const arr = [1, 2];
console.dir(arr);
console.dir(Array)
위 사진은 출력 결과의 일부입니다.
왼쪽은 arr 변수를 출력한 결과입니다. 첫줄에는 Array(2)라고 표기되고 있습니다. Array라는 생성자 함수를 원형으로 생성되었고, length가 2임을 알 수 있네요.
이제 오른쪽을 보죠.
첫 줄에는 함수라는 의미의 f가 표시되어 있네요. 둘째 줄부터는 함수의 기본적인 프로퍼티들이 들어있고요. 또한 Array 함수의 정적 메서드인 from, isArray, of 등도 보이네요.
prototype을 열어보니 왼쪽의 __proto__
와 완전히 동일한 내용으로 구성되어 있군요! 이는 인스턴스의 __proto__
프로퍼티는 생성자 함수의 prototype 프로퍼티를 참조한다는 것을 명확히 보여주네요.
이번에는 객체의 내부 구조를 살펴보죠.
console.dir({ a: 1 });
첫 줄을 통해 Object의 인스턴스임을 알 수 있고, 프로퍼티 a의 값 1이 보이네요. constructor는 생성자 함수인 Object를 가리키고 있습니다. __proto__
는 Object의 prototype 프로퍼티를 참조하겠네요.
이번엔 다시 한 번 배열의 구조를 살펴볼게요.
__proto__
안에 또다시 __proto__
가 등장하고, 객체의 __proto__
와 동일한 내용으로 이뤄져 있네요. 왜 그럴까요? 바로 prototype 객체가 "객체"이기 때문입니다. 기본적으로 모든 객체의 __proto__
에는 Object.prototype이 연결됩니다.
이처럼 어떤 데이터의 __proto__
프로퍼티 내부에 다시 __proto__
프로퍼티가 연쇄적으로 이어진 것을 프로토타입 체인이라고 하고, 이 체인을 따라 검색하는 것을 프로토타입 체이닝이라고 합니다.
어떤 메서드를 호출하면 자바스크립트 엔진은 데이터 자신의 프로퍼티들을 검색해서 원하는 메서드가 있으면 그 메서드를 샐행하고, 없으면 __proto__
를 검색해서 있으면 그 메서드를 실행하고, 없으면 다시 __proto__
를 검색해서 실행하는 식으로 진행합니다.
어떤 생성자 함수이든 prototype은 반드시 객체이기 때문에 Object.prototype이 언제나 프로토타입 체인의 최상단에 존재하게 됩니다. 먹이사슬로 따지면 먹이사슬의 최상위 존재이죠. 따라서 객체에서만 사용할 메서드는 프로토타입 객체 안에 정의할 수가 없죠. 왜냐하면 객체에서만 사용할 수 있는 메서드를 Object.prototype 내부에 정의한다면 다른 데이터 타입도 해당 메서드를 사용할 수 있기 때문입니다.
이 같은 이유로 객체만을 대상으로 동작하는 객체 전용 메서드들은 부득이 Object.prototype이 아닌 Object에 정적 메서드로 부여할 수밖에 없죠. 반대로 Object.prototype에는 어떤 데이터에서도 활용할 수 있는 범용적인 메서드들만 있습니다.
코어 자바스크립트(출처)