자바스크립트는 프로토타입 기반 언어이다. 프로토타입 기반 언어란 어떤 객체를 원형(prototype)으로 삼고 이를 복제(참조)함으로써 클래스 기반 언어에서 상속하는 것과 비슷한 효과를 얻는다.
const instance = new Constructor();
__proto__
라는 프로퍼티가 자동으로 부여되는데__proto__
또한 객체이다.prototype 객체 내부에는 instance가 사용할 메서드가 저장되어있는데 인스턴스의 숨겨진 프로퍼티인 __proto__
를 통해 이 메서드에 접근할 수 있다.
(__proto__
를 통해 메서드에 접근하는 것보다 Object.getPrototypeOf() / Object.create()를 이용해 접근하는 것이 범용성이 높다.)
const Person = function (name) {
this._name = name;
};
Person.prototype.getName = function() { //getName 메서드
return this._name;
};
const suzi = new Person('Suzi');
suzi.__proto__.getName(); // undefined
어떤 함수를 '메서드로서' 호출할 때는 메서드명 바로 앞의 객체가 곧 this가 된다. 따라서 suzi.__proto__.getName()
에서 getName 함수 내부에서의 this는 suzi가 아니라 suzi.__proto__
라는 객체가 되는 것이다. 이 객체 내부에는 name 프로퍼티가 없으므로 undefined를 반환한 것이다.
__proto__
객체에 name 프로퍼티가 있도록 하면 정상적으로 원하는 값이 출력된다.
const suzi = new Person('Suzi');
suzi.__proto__._name = "SUZI__proto__";
suzi.__proto__.getName(); // SUZI__proto__
__proto__
없이 instance에서 바로 메서드를 쓰면 this가 가리키는 것을 instance로 만들 수 있다. __proto__
가 생략 가능한 프로퍼티이기 때문이다.
suzi.__proto__.getName();
-> suzi(.__proto__).getName();
-> suzi.getName(); // Suzi
따라서 다음과 같은 도식이 성립된다.
예를 들어 array에서 isArray() 메서드는 프로퍼티 내부에 있지 않아 인스턴스가 직접 호출할 수 없고 Array 생성자 함수에서 직접 접근해서 실행해야한다.
반면 push() 메서드는 프로프티 내부에 있어 인스턴스가 직접 호출 가능하다.
const arr = [1, 2];
Array.isArray(arr); // (O)
arr.isArry(); // (X) TypeError
arr.push(3) // (O)
생성자 함수의 프로퍼티인 prototype 객체 내부에는 constructor라는 프로퍼티가 있다. prototype에 있으니까 prototype를 참조하는 instance의 __proto__
객체 내부에도 있다.
constructor 프로퍼티는 생성자 함수(자기 자신)을 참조하여 인스턴스로부터 그 원형이 무엇인지를 알 수 있는 수단이 된다.
한편 constructor는 읽기 전용 속성이 부여된 예외적인 경우(✱기본형 리터럴 변수 - number, string, boolean)을 제외하고는 값을 바꿀 수 있다. 하지만 constructor를 변경하더라도 참조 대상이 변경될 뿐 이미 만들어진 인스턴스의 원형이 바뀐다거나 데이터 타입이 변하는 것은 아니다. 따라서 어떤 인스턴스의 생성자 정보를 알아내기 위해 constructor 프로퍼티에 의존하는 것이 안전하지는 않다.
동일한 이름의 프로퍼티 또는 메서드를 가지고 있으면 자신으로부터 가장 가까운 메서드부터 접근한다.
this가 instance의 __proro__
에 접근하려면 call, apply를 사용하면된다.
어떤 데이터의 __proro__
프로퍼티 내부에 다시 __proro__
가 연쇄적으로 이어진 것을 프로토타입 체인이라고 한다. 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝이라고 한다.
데이터 타입별 프로토타입 체인은 다음과 같다.
Object.prototype은 언제나 프로토타입 체인의 최상단에 존재한다.
다만 예외적으로 Object.create(null)로 __proro__
가 없는 객체를 생성하면 Object.prototype에 접근할 수 없는 경우가 있다.
사용자가 대각선의 __proro__
을 연결하면 무한대로 체인 관계를 이어나갈 수 있다.
대각선의 __proro__
를 연결하려면 __proro__
가 가리키는 대상, 즉 생성자함우의 prototype이 연결하고자 하는 상위 생성자의 instance를 바라보도록 해주면 된다.