[JavaScript] 프로토타입

빵호·2021년 11월 11일
0

JavaScript

목록 보기
17/28
post-thumbnail

프로토타입

프로토타입의 개념 이해

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

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

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

Person이라는 생성자의 함수의 prototype에 getName이라는 메서드를 지정하였다. 이제 Person의 인스턴스는 __proto__ 프로퍼티를 통해 getName을 호출할 수 있다.

var suzi = new Person('Suzi');
suzi.__proto__.getName(); // undefined

이유는 instance의 __proto__가 Constructor의 prototype 프로퍼티를 참조하므로 둘은 각은 객체를 바라보기 때문이다.

Person.prototpye === suzi.__proto__ // true

그런데 메서드를 호출하니 undefined가 나왔다. 중요한 것은 에러가 발생하지 않고 undefined가 나왔다는 것이고 이는 해당 변수가 호출할 수 있는 함수라는 것을 의미한다. 그런데 왜 Suzi라는 값이 나오지 않고 undefined가 나오는걸까? 이유는 this에 바인딩된 대상이 잘못 지정됐기 때문이다. 함수를 메서드로서 호출하면 메서드명 바로 앞의 객체가 this가 된다. 그러니까 위 코드의 경우에는 this가 suzi가 아닌 suzi.__proto__ 라는 객체가 된다. 이 객체 내부에는 name 프로퍼티가 없으므로 undefined를 반환하는 것이다.

var suzi = new Person('Suzi');
suzi.__proto__._name = 'SUZI__proto__';
suzi.__proto__.getName(); // SUZI__proto__

__proto__ 객체에 name 프로퍼티가 있는 경우에는 UZI__proto__가 출력이 된다. 관건은 this이다.

var suzi = new Person('Suzi', 28);
suzi.getName(); // Suzi
var iu = new Person('Jieun', 28);
iu.getName(); // Jieun

this를 인스턴스로 하는 방법은 __proto__ 없이 인스턴스에 곧바로 메서드를 쓰면 된다. 히지만 메서드가 호출되고 원하는 값이 나오는게 이상하게 느껴진다. 이게 가능한 이유는 __proto__가 생략 가능한 프로퍼티이기 때문이다. __proto__를 생략하지 않으면 this는 suzi.__proto__를 가르키지만, 이를 생략하면 suzi를 가리킨다. 그럼 suzi.__proto__의 메서드인 getName이 실행 가능하면서 this는 suzi를 바라보게 된다.

요약

자바스크립트는 함수에 자동으로 객체인 prototype 프로퍼티를 생성해 놓는데, 해당 함수를 생성자 함수로서 사용할 경우 그로부터 생성된 인스턴스에는 숨겨진 프로퍼티인 __proto__가 자동으로 생성되며, 이 프로퍼티는 생성자 함수의 prototype 프로퍼티를 참조합니다. __proto__ 프로퍼티는 생략 가능하기 떄문에 생성자 함수의 prototype에 어떤 메서드나 프로퍼티가 있다면 인스턴스에서도 해당 메서드나 프로퍼티에 접근할 수 있게 된다.

var Constructor = function (name) {
  this.name = name;
};
Constructor.prototype.method = function() {};
Constructor.prototype.property = 'Constructor Protoype Property';

var instance = new Constructor('Instance');
console.dir(Constructor);
console.dir(instance);

Constructor와 instance의 디렉터리 구조를 살펴보면 instance의 __proto__ 가
Constructor의 prototype을 참조하는걸 알수있다. 그런데 instance의 출력 결과에 Constructor 나오고 있다. 이유는 어떤 생성자 함수는 인스턴스는 해당 생성자 함수의 이름을 표기함으로써 해당 함수의 인스턴스임을 표기하기 때문이다.

var arr= [1, 2];
console.dir(arr);
console.dir(Array);


내장 생성자 함수인 Array의 경우도 __proto__와 동일한 내용으로 구성돼 있다. 인스턴스의 __proto__가 Array.prototype을 참조하기 때문에 push, pop 등의 메서드를 호출할 수 있다.

프로토타입 체인

메서드 오버라이드

만약 인스턴스에 __proto__와 동일한 이름의 프로퍼티 또는 메서드를 가지고 있으면 어떻게 될까?

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

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

iu.__proto__.getName이 아닌 iu 객체에 있는 getName 메서드가 호출되었다. 여기서 일어난 현상을 메서드 오버라이드라고 하고 메서드 위에 메서드를 덮어씌웠다는 표현이다. 자바스크립트 엔진이 getName 이라는 메서드를 찾는 방식은 자신의 프로퍼티를 검색하고,없으면 __proto__를 검색하는 순서로 진행 된다. 그래서 __proto__에 있는 메서드는 자신에게 있는 메서드에 순서가 밀려 호출되지 않은 것이다.

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

오버라이드는 원본을 교체하는게 아닌 얹는 방법이여서 위의 코드처럼 원본에 접근할 수 있다.

console.log(iu.__proto__.getName.call(iu)); // 지금

일반적으로는 메서드가 오버라이드된 경우 가장 가까운 메서드에만 접글할 수 있지만, call 이나 apply를 사용하면 다음으로 가까운 __proto__의 메서드도 우회적으로 접근이 가능하다.

프로토타입 체인


배열 리터럴의 __proto__에는 pop,push 등의 배열 메서드 및 constructor가 있다. 추가로 이 __proto__ 안에는 또 다시 __proto__가 등장한다. 이유는 prototype 객체가 객체이기 때문이다. 모든 객체의 __proto__ 에는 Object.prototype이 연결된다. 그렇기 때문에 Object.prototype 내부의 메서드를 실행할 수 있는 것이다.

var arr = [1, 2];
arr(.__proto__).push(3);
arr(.__proto__)(.__proto__).hasOwnProperty(2); // true

__proto__ 프로퍼티 내부에 다시 __proto__ 프로퍼티가 연쇄적으로 이어진것을 프로토타입 체인(prototype chain)이라 하고, 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝(prototype chaining)이라고 한다. 프로토타입 체이닝은 메서드 오버라이드와 마찬가지로 메서드를 호출하면 자신의 프로퍼티들을 검색 후 메서드가 있으면 그 메서드를 실행하고 없으면 __proto__를 계속 검색하여 실행한다.

배열뿐만 아니라 자바스크립트의 모든 데이터는 위 사진처럼 동일한 형태의 프로토타입 체인 구조를 지닌다.

객체 전용 메서드의 예외사항

어떤 생성자 함수이든 prototype은 반드시 객체이기 떄문에 Object.prototype이 언제나 프로토타입 체인의 최상단에 존재한다. 따라서 객체에서 사용할 메서드는 프로토타입 객체 안에 정의할 수가 없다. Object.prototype 내부에 정의하면 다른 데이터 타입도 해당 메서드를 사용할 수 있게 되기 떄문이다. 반대로 같은 이유에서 Object.prototype에는 어떤 데이터에서도 활용할 수 있는 메서드들(toString, valueOf...)만 있다.

다중 프로토타입 체인

기본 내장 데이터 타입들은 모두 프로토타입 체인이 1단계(객체)이거나 2단계(나머지)로 끝나는 경우만 있었지만 사용자가 새롭게 만드는 경우 그 이상도 가능하다. 방법은 생성자 함수의 prototype이 연결하고자 하는 상위 생성자 함수의 인스턴스를 바로보게끔 해주면 된다.

profile
늘 한결같이 꾸준히

0개의 댓글