Core Javascript - 프로토타입

김규빈·2021년 3월 2일
1
post-thumbnail

이 글은 Core Javascript를 읽고 내용을 정리하였습니다. 2회독 하였지만 잘못된 내용의 지적을 감사히 받겠습니다.

프로토타입이 뭐얌

자바스크립트는 프로토타입기반의 언어이다. 프로토타입의 정의와 작동원리를 제대로 이해하지 못한채 자바스크립트를 학습한다면 반쪽짜리 JS개발자다라고 생각될 만큼 중요한 개념인것 같다. 정신차려
객체지향 프로그래밍의 대표적인 기반은 프로토타입기반과 클래스 기반 프로그래밍이 있다. 클래스 기반 언어에서는 '상속'을 사용하지만 프로토타입 기반 언어에서는 어떤 객체를 원형으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻습니다. 유명한 프로그래밍 언어의 상당수가 클래스 기반인 것에 비교하면 프로토타입기반은 꽤나 독특한 개념이라 할 수 있다.

이 삼각형을 이해한다면 프로토타입을 이해했다고 봐도 무방하다.

- 어떤 생성자 함수(constructor)를 new 연산자와 함꼐 호출하면
- Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스가 생성된다.
- 이때 인스턴스에는 __proto__라는 프로퍼티가 자동으로 부여되는데,
- 이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조한다.

Prototype은 객체이다. 이를 참조하는 __proto__ 역시 객체다. Prototype 객체 내부에는 인스턴스가 사용할 메서드를 지정한다. 그러면 인스턴스에서도 숨겨진 프로터피인 __proto__를 통해서 이 메서드들에게 접근한다. 우리는 항상 이 방법으로 메서드를 사용하고 있었다. 그런데 왜 생소하냐? __proto__는 생략이 가능하기 때문에 코드를 통해 예시를 보자.

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

이제 Person의 인스턴스는 __proto__프로퍼티를 통해 getName을 호출할 수 있다.

let suzi = new Person("suzi")
suzi.__proto__.getName(); 	//undefined

왜냐하면 인스턴스에 __proto__가 Constructor의 prototype 프로퍼티를 참조하므로 결국 둘은 같은 객체를 바라보게 된다.

Person.prototype === suzi.__proto__  	//true

메서드 호출 결과로 undefined가 나온 점에 주목해 보자. suzi라는 값이 나오지 않은 것보다는 에러가 나지 않은점이 우선이다. 어떤 변수를 실행해 undefined가 나왔다는 것은 이 변수가 '호출할 수 있는 함수'에 해당한다. 만약 호출할 수 없었을 경우 typeError가 발생했을 것이다. 그렇다면 왜 suzi라는 값을 반환하지 않았을까. 바로 this의 바인딩이 잘못됬다는 것이다. 어떤 함수를 '메서드로서' 호출할 때는 메서드명 바로 앞의 객체가 곧 this가 된다. 그러니까 thomas.__proto__.getName()에서 함수 내부에서의 this는 thomas가 아니라 thomas.__proto__라는 객체가 되기 때문에 찾고자 하는 식별자가 정의 되지 않은 것이다. 그렇다면 매번 this를 정의 해줘야 하냐? 아니다. 우리는 그렇게 메서드를 호출하지 않았다. 왜냐하면 __proto__는 생략이 가능하기 때문이다. thomas.getName()을 호출하게 되면 호출 주체 thomas가 this로 바인딩 되기 때문에 우리가 예상했던 결과가 출력된다.

정리해보자면 생성자 함수의 prototype에 어떤 메서드나 프로터피가 있다면 인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티에 접근이 가능하게 된다. 저 삼각형에서 대각선으로 참조하고 있다는 점을 기억하자. __proto__는 생략이 가능하기 때문에 우리는 항상 proto없이 메서드를 사용해왔다.

프로토 타입의 체인상 가장 마지막에는 언제나 object.prototype이 있다(Arr도 결국엔 객체다). 여기에 예외사항이 있는데 object.create를 이용하면 object.prototype의 메서드에 접근할 수 없는 경우가 있는데 object.create(null)__proto__가 없는 객체를 생성하게 된다.

let _proto = object.create(null)
_proto.getValue = function(key){
	return this[key]
};
let obj = Object.create(_proto);
obj.a = 1;
console.log(obj.getValue("a"))		// 1
console.dir(obj)

obj를 출력해보면 __proto__에는 오직 getValue메서드만이 존재하고 나머지 프로퍼티는 보이질 않는다. 이 방식으로 만든 객체는 일반적인 데이터에서 반드시 존재하던 내장 메서드 및 프로퍼티들이 제거됨으로써 기본 기능에 제약이 생긴 대신, 객체 자체의 무게가 가벼워짐으로써 성능상 이점을 가진다.

결과

어떤 생성자 함수를 new 연산자와 함께 호출하면 Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스가 생성되는데, 이 인스턴스에는 __proto__ 라는, Constructor의 prototype프로퍼티를 참조하는 프로퍼티가 자동으로 부여된다. __proto__는 생략이 가능한 속성이라서, 인스턴스는 Constructor.prototype의 메서드를 마치 자기 자신의 메서드인 것처럼 호출 할 수 있다.

Constructor.prototype에는 Constructor라는 프로퍼티가 있는데, 이는 다시 생성자 함수자신을 가리킨다. 이 프로퍼티는 인스턴스가 자신의 생성자 함수가 무엇인지를 알고자 할 때 필요한 수단이다.

직각삼각형의 대각선 방향, 즉 __proto__ 방향을 계속 찾아가면 최종적으로는 Object.prototype에 당도하게 된다. 이런 식으로 __proto__안에 다시 __proto__를 찾아가는 과정을 프로토타입 체이닝이라고 하며, 이 프로토타입 체이닝을 통해 각 프로토타입 메서드를 자신의 것처럼 호출할 수 있다. 이때 접근 방식은 자신으로부터 가장 가까운 대상부터 점차 먼 대상으로 나아가며, 원하는 값을 찾으면 검색을 중단한다.

Constructor.prototype에는 모든 데이터 타입에서 사용할 수 있는 범용적인 메서드만이 존재하며, 객체 전용 메서드는 여느 데이터 타입과는 달리 Object 생성자 함수에 스태틱하게 담겨있다.

profile
FrontEnd Developer

0개의 댓글