Array 생성자 함수를 이용하여 배열 인스턴스를 하나 만들었다. 사용자는 그 배열에 새로운 값을 넣을 때 push 메서드를 사용할 수 있다. 그런데 이 push 메서드를 호출하는 주체는 사용자가 생성자 함수로 만든 인스턴스이다. 그렇다면 이 인스턴스는 push라는 메소드를 가지고 있다는 뜻이 된다. Array 생성자 함수은 어떻게 인스턴스에게 메서드를 전달해주는 것인가? 프로토타입은 이 의문에 대한 해답을 제시해준다.
constructor생성자 함수를 이용하여 new 연산자로 새롭게 instance인스턴스를 만들어 보자.
그러면 그 instance인스턴스에는 constructor생성자 함수에 있는 prototype프로토타입 프로퍼티의 내용이 instance인스턴스의 [[prototype]]프로토타입 프로퍼티로 참조 형태로 전달되게 된다.
즉, constructor.prototype과 instance.[[prototype]]은 같은 객체를 바라본다. 다만 instance.[[prototype]]은 접근이 가능하지 않고 오직 정보를 보여주기만 한다. 실제 동작상으로는 instance와 동일시 된다는 뜻이다.
[1, 2, 3]
여기 가상의 배열이 하나 있다. 이 배열은 리터럴로 생성했거나 Array 생성자함수와 new 연산자를 이용하여 만들 수 있지만, 내부적으로는 Array 생성자함수를 이용한 것과 동일하게 동작한다.
Array 생성자함수에는 다양한 프로퍼티들이 존재한다. 이 중에는 prototype프로토타입이 존재하고, 이 것은 곧 배열 리터럴의 [[prototype]]프로토타입으로 연결이 된다. prototype프로토타입 하위에도 여러 내용들이 담겨있다. concat(), filter()등의 배열 메서드들이 그것이다.
만약 정수와 같이 생성자 함수와 상관 없는 변수를 가지고 메서드를 사용하려 한다면? 자바스크립트는 임시로 Number 생성자 함수의 인스턴스를 만들어 동일한 결과를 만들게 한 다음, 인스턴스를 제거한다. 기본형 데이터 타입은 모두 이 방식에 해당한다.
참조형 데이터 타입은 모두 생성자 함수를 이용하여 인스턴스로 만들어지기 때문에 프로토타입을 이용한 방법을 사용한다.
즉, 모든 데이터 타입은 메서드에 접근할 때 prototype을 이용하게 된다는 뜻이다. null과 undefined를 제외한 모든 데이터 타입은 메서드를 직접 가지고 있지 않고, constructor생성자 함수의 prototype 프로퍼티에 있는 메서드들을 [[prototype]]을 통해서 자기 것처럼 호출하여 사용한다. constructor생성자 함수의 prototype 프로퍼티에는 각 데이터 타입에 해당하는 전용 메서드들이 정의되어 있다.
instance.__proto__;
단! 이 방식은 각 웹 브라우저들의 호환성 문제로 인해 임시적으로 만든 방식이라 공식적으로 사용을 권장하고 있지 않고 있다.
Object.getPrototypeOf(instance);
공식적으로 권장되고 있는 방법.
instance.proto
Object.getPrototypeOf(instance)
instance
Constructor.prototype
Constructor
instance.proto.constructor
Object.getPrototypeOf(instance).constructor
instance.constructor
Constructor.prototype.constructor
function Person(inputName, inputAge) {
this.name = inputName;
this.age = inputAge;
}
var kim = new Person('김', 10);
var kim1 = new kim.__proto__.constructor('김_복제1', 10);
var kim2 = new Object.getPrototypeOf(kim).constructor('김_복제2', 10);
var kim3 = new kim.constructor('김_복제3', 10);
var kim4 = new Person.prototype.constructor('김_복제4', 10);
위 코드는 설명한 방법으로 prototype과 Constructor에 접근하는 예시이다. kim1에서 kim4까지는 prototype까지 도달한 다음에 Constructor에 접근하는 5가지 방법을 통해 Constructor에 다가간 것으로 방법은 다르지만 동일한 생성자 함수로 만들어진 인스턴스이고, 동일한 대상에 접근하고 있는 것이다.
var lee = new Person('이', 10);
var choi = new Person('최', 10);
lee.setOlder = function() {
this.age += 1;
}
lee.getAge = function() {
return this.age;
}
choi.setOlder = function() {
this.age += 1;
}
lee.getAge = function() {
return this.age;
}
여기 2개의 인스턴스가 있고, 각각의 인스턴스는 대상의 나이 값을 조정하는 메서드와 나이 값을 가져오는 메서드를 가지고 있다. 그런데 이 메서드들은 완전히 동일한 내용을 지니고 있다. 이런 반복을 줄여줄 방법이 없을까? prototype를 이용하여 다음과 같이 반복 메서드들을 압축해볼 수 있다.
Person.prototype.setOlder = function() {
this.age += 1;
}
Person.prototype.getAge = function() {
return this.age;
}
이렇게 생성자 함수 Person의 prototype에 메서드들이 추가되고, 인스턴스를 만들 때마다 [[prototype]]를 통해 메서드들을 참조할 수 있도록 만들어 준다. 인스턴스들이 모두 동일하게 가져야하는 정보를 생성자 함수만 갖게 함으로써 메모리 사용 효율이 급격하게 높아지는 것이다. 또 특정 집단의 공통된 속성을 파악할 수 있게 되기도 한다.
constructor생성자 함수의 prototype프로토타입 프로퍼티는 '객체'이다. 이는 결국 prototype 또한 Object 생성자 함수의 new 연산으로 생성되는 인스턴스라는 뜻이다. 그렇다면 prototype 역시 Object 생성자 함수의 prototype과 연결이 되어 있는 뜻이 된다. 모든 데이터 타입의 prototype은 Object 생성자 함수의 prototype과 '연결'되어 있는데, 이를 prototype chaining이라 칭한다.
Object 생성자 함수의 prototype에는 이상하게도 Object객체의 전용 메서드들이 포함되어있지 않다. prototype chaining으로 인해 Object.prototype에 담긴 내용들은 인스턴스로 '반드시' 전달되기 때문. 그렇기에 Object객체의 전용 메서드들은 Object.prototype가 아닌, Object에 포함되어있다. 그래서 객체 관련한 명령어들은 Object를 이용하여 메서드를 호출하고, 메서드의 매개변수로 객체를 넘기게 되어있다.
obj.values();
이 아니라.
Object.values(obj);
가 되는 것.
반면 다른 데이터 타입의 경우 객체에서 메서드들을 바로 호출할 수 있다.
char.valueOf();
arr.toString();
인스턴스는 자기 자신에게 메서드가 존재하는지 확인한다.
당연히 메서드 자신에게 존재할 리가 없으므로 생성자 함수의 prototype을 참조하게 된다.
만약 생성자 함수의 prototype에도 메서드가 없으면, prototype의 생성자 함수인 Object로 넘어가 Object.prototype를 살펴본다.
Object.prototype에도 메서드가 없으면, 에러가 발생한다.
자기 자신을 시작으로 가장 가까운 곳부터 살펴보는 Scope Chain과 같은 개념으로 동작하는 것이다.
정재남 - 코어 자바스크립트.