[코어 자바스크립트] 6.프로토타입

홍예찬·2021년 1월 16일
0
post-thumbnail

자바스크립트는 프로토타입 기반 언어입니다. 클래스 기반의 언어에서는 상속을 사용하지만, 프로토타입 기반 언어에서는 어떤 객체를 원형으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻습니다. (유명한 대부분의 프로그래밍 언어의 상당수가 클래스 기반입니다.)

6-1 프로토타입의 개념 이해

① constructor, prototype, instance


var instance = new Constructor();
  1. 어떤 생성자 함수(Constructor)를 new 연산자와 함께 호출하면
  2. Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스(instance)가 생성됩니다.
  3. 이때 instance에는 __proto__라는 프로퍼티가 자동으로 부여됩니다.
  4. 이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조합니다.

여기서 새롭게 등장한 prototype이라는 프로퍼티와 __proto__라는 프로퍼티의 관계가 프로토타입 개념의 핵심입니다.
prototype은 객체입니다. 이를 참조하는 __proto__ 역시 당연히 객체입니다.
prototype 객체 내부에는 인스턴스가 사용할 메소드를 저장합니다. 그러면 인스턴스에서도 숨겨진 프로퍼티인 __proto__를 통해 이 메소드들에 접근할 수 있게 됩니다.

예를 들어 Person이라는 생성자 함수의 prototype에 getName이라는 메소드를 지정했다 보겠습니다.


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

var suzi = new Person('Suzi');

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

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

② consturctor 프로퍼티

생성자 함수의 프로퍼티인 prototype 객체 내부에는 constructor라는 프로퍼티도 존재합니다. 인스턴스의 __proto__ 객체 내부에도 마찬가지입니다. 이 프로퍼티는 단어 그대로 원래 생성자 함수(자기 자신)를 참조합니다. 이를 가지고 있는 이유는 인스턴스로부터 그 원형이 무엇인지를 알 수 있는 수단이기 떄문입니다.


var arr = [1,2];
Array.prototype.constructor === Array  //true
arr.__proto__.constructor === Array    //true
arr.constructor === Array	       //true

var arr2 = new arr.constructor(3, 4);
console.log(arr2);	// [3, 4]

6-2 프로토타입 체인

① 메소드 오버라이드

prototype 객체를 참조하는 __proto__를 생략하면 인스턴스는 prototype에 정의된 프로퍼티나 메소드를 자신의 것처럼 사용할 수 있습니다. 그런데 만약 인스턴스가 동일한 이름의 프로퍼티 또는 메소드를 가지고 있는 상황이라면 어떨까요?


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

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

var iu = new Person('지금');
iu.getName = function () {
  return '바로 ' + this.name
}

console.log(iu.getName());	//바로 지금
  1. 여기서는 iu.__proto__.getName이 아닌 iu 객체에 있는 getName 메소드가 호출됐습니다.
  2. 즉, __proto__에 있는 메소드는 자신에게 있는 메소드보다 검색 순서에서 밀려 호출되지 않은 것입니다.

이러한 현상을 메소드 오버라이드라고 합니다. 메소드 위에 메소드를 덮어씌웠다는 표현입니다. 즉, 원본을 다른 대상으로 교체하는 것이 아니라 원본이 그대로 있는 상태에서 다른 대상을 그 위에 얹는 이미지를 떠올리면 되겠습니다.

자바스크립트 엔진이 getName 메소드를 찾는 방식은 가장 가까운 대상인 자신의 프로퍼티를 검색하고 없을 경우 그 다음으로 가까운 대상을 검색하는 순서로 진행되기 때문이죠.

그렇다면 메소드 오버라이딩이 이루어져 있는 상황에서 prototype에 있는 메소드에 접근하려면 어떻게 해야할까요?


//(1)
console.log(iu.__proto__.getName())	//undefined

//(2)
Person.prototype.name = '이지금';
console.log(iu.__proto__.getName());	//이지금

//(3)
console.log(iu.__proto__.getName.call(iu)); 	//지금
  1. undefined가 출력된 이유는 this가 prototype 객체를 가리키는데 prototype에는 name 프로퍼티가 없기 때문입니다.
  2. prototype에 name 프로퍼티를 추가한다면 그 값을 출력할 것입니다.
  3. this가 prototype을 바라보는 것이 아니라 인스턴스를 바라보도록 바꿔줄수도 있습니다. 앞선 this장에서 학습했던 call 또는 apply 메소드를 사용하면 되겠습니다.

② 프로토타입 체인

어떤 데이터의 __proto__프로퍼티 내부에 다시 __proto__프로퍼티가 연쇄적으로 이어진 것을 프로토타입 체인이라 하고, 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝이라고 합니다.

이는 메소드 오버라이드와 동일한 맥락입니다. 어떤 메소드를 호출하면 자바스크립트 엔진은 자신의 프로퍼티를 검색해서 원하는 메소드가 있으면 그 메소드를 실행하고 없는 경우 __proto__를 검색해서 있으면 그 메소드를 실행하는 방식으로 진행합니다.


var arr = [1,2];
Array.prototype.toString.call(arr);	//1,2
Object.prototype.toString.call(arr);	//[object Array]
arr.toString();	//1,2

arr.toString = function () {
  return this.join('_');
};
arr.toString();	//1_2
  1. arr 변수는 배열이므로 arr.__proto__는 Array.prototype을 참조하고, Array.prototype은 객체이므로 Array.prototype.__proto__는 Ojbect.prototype을 참조합니다.
  2. 이는 배열뿐만 아니라 자바스크립틔의 모든 데이터가 프로토타입 체인 구조를 지니게 됩니다.

위의 글은 온전히 학습 목적을 위해 작성한 글입니다. 위 내용의 모든 지적 재산권,저작권은 코어 자바스크립트 저자에게 있으며, 무단 전재 및 재배포를 금합니다.

profile
내실 있는 프론트엔드 개발자가 되기 위해 오늘도 최선을 다하고 있습니다.

0개의 댓글