코딩쌀롱

[코어 자바스크립트] 6-2 프로토타입 체인 본문

[코어 자바스크립트] 6-2 프로토타입 체인

이브✱ 2020. 12. 30. 17:55

메서드 오버라이드

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

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

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

console.log(iu.getName());  // 바로 지금

 prototype에도 getName 메서드가 있고, 인스턴스인 iu에도 getName 메서드가 있다. iu.getName()을 호출하면 어떤 메서드를 실행할까? 자바스크립트 엔진이 메서드를 찾는 방식은 가장 가까운 대상인 자신의 프로퍼티를 검색하고, 없으면 그 다음으로 가까운 대상인 __proto__를 검색하는 순서로 진행된다. 그래서 위의 경우 가장 가까운 대상인 자신의 메서드를 실행한다.

 이런 현상을 '메서드 오버라이드'라고 한다. 메서드 위에 메서드를 덮어씌웠다는 표현으로, 원본을 제거하고 교체하는 것이 아니라 원본이 있는 상태에서 그 위에 얹은 것이다. 즉, 다르게 말하면 원본의 메서드가 없어진 것이 아니니 원본의 메서드에도 접근이 가능하다. 

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

 메서드가 오버라이드된 경우에는 자신으로부터 가장 가까운 메서드에만 접근할 수 있지만, 그 다음으로 가까운 __proto__의 메서드도 우회적인 방법을 통해서이긴 하지만 접근이 불가능한 것은 아니다.(교체X, 얹는O)

 

프로토타입 체인

배열 리터럴의 __proto__에는 또 __proto__가 나온다. 그 이유는 __proto__가 참조하는 prototype이 객체이기 때문이다. 기본적으로 모든 객체의 __proto__에는 Object.prototype이 연결된다.

〔코어 자바스크립트〕 책에 있는 도식을 참고해 그렸습니다.

 [ 1, 2 ]는 배열이므로 .__proto__는 Array.prototype을 참조하고, Array.prototype은 객체이므로 Array.prototype.__proto__는 Object.prototype을 참조한다. 그리고 __proto__는 생략 가능하기 때문에, 배열이 Array.prototype 내부의 메서드를 마치 자신의 것처럼 실행할 수 있었다. 마찬가지로 __proto__를 한 번 더 따라가면 Object.prototype 내부의 메서드도 자신의 것처럼 실행할 수 있다.

 

 어떤 데이터의 __proto__ 프로퍼티 내부에 다시 __proto__ 프로퍼티가 연쇄적으로 이어진 것을 프로토타입 체인이라 하고, 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝이라고 한다. 어떤 메서드를 호출하면 자바스크립트 엔진은 자신의 프로퍼티들을 검색해서 원하는 메서드가 있으면 그 메서드를 실행하고, 없으면 __proto__를 검색해서 있으면 그 메서드를 실행하고, 없으면 다시 __proto__를 검색해서 실행하는 식으로 진행한다.

 

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

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

 

 객체만을 대상으로 동작하는 객체 전용 메서드들은 부득이 Object.prototype이 아닌 Object에 스태틱 메서드로 직접 부여할 수 밖에 없었다. Object.prototype이 참조형 데이터뿐 아니라 기본형 데이터조차 __proto__에 반복 접근함으로써 도달할 수 있는 최상위 존재이기 때문이다. 반대로 생각해보면 Object.prototype에는 어떤 데이터 타입에서도 활용할 수 있는 범용적인 메서드들만 있다.(toString, hasOwnProperty, valueOf, isPrototypeOf...)

 

스태틱 메서드는 생성자 Constructor에 직접 접근해야 호출할 수 있기 때문에 인스턴스를 인자로 직접 주입해서 구현하도록 되어있다. instance.freeze()가 아니라, Object.freeze(instance)로 해야한다.

 

다중 프로토타입 체인

 대각선의 __proto__를 연결해나가기만 하면 무한대로 체인 관계를 이어나갈 수 있다. 대각선의 __proto__를 연결하는 방법은 __proto__가 가리키는 대상, 즉 생성자 함수의 prototype이 연결하고자 하는 상위 생성자 함수의 인스턴스를 바라보게끔 해주면 된다.

const Grade = function () {
   const args = Array.prototype.slice.call(argements);
   for(let i = 0; i < args.length; i++) {
      this[i] = args[i];
   }
   this.length = args.length;
};

const g = new Grade(100, 80);

Grade의 인스턴스는 배열의 형태를 지니지만, 배열의 메서드를 사용할 수 없는 유사배열객체이다. 인스턴스에서 배열 메서드를 직접 쓸 수 있게끔 하려면, Grade.prototype이 배열의 인스턴스를 바라보게 하면 된다.

Grade.prototype = [];

위 코드로 인해 아래와 같은 프로토타입 체인 형태를 띠게 된다.

〔코어 자바스크립트〕 책에 있는 도식을 참고해 그렸습니다.

이제는 Grade의 인스턴스인 g에서 직접 배열의 메서드를 사용할 수 있다.

console.log(g); // Grade(2) [100, 80]

g.pop();
console.log(g); // Grade(1) [100]

g.push(90);
console.log(g); // Grade(2) [100, 90]

 

 

 


정재남 작가님의〔 코어 자바스크립트 〕책을 공부하며 정리했습니다.

 

Comments