자바스크립트 입문_메서드 오버라이드, 프로토타입 체인

자바스크립트 2021. 5. 19. 14:53

(본 포스팅은 위키북스의 '코어자바스크립트' 책을 공부하면서 작성되었습니다_내돈내산)

 

< 메서드 오버라이드(method overide) >

  • 인스턴스가 동일한 이름의 프로퍼티 또는 메서드를 가지고 있는 상황에 메서드 위에 메서드가 덮어씌워지는 현상. 원본을 제거하고 다른 대상으로 교체하는 것이 아니라, 워본이 그대로 있는 상태에서 다른 대상을 그위에 얹음.
let Person = function(name) {
  this.name = name;
};
Person.prototype.getName = function() {
  return this.name;
};

let iu = new Person('지금');
iu.getName = function() {
  return '바로 ' + this.name;
};
console.log(iu.getName()); // 바로 지금
  1. 위의 코드를 보았을 때, iu.__proto__.getName이 아닌 iu 객체에 있는 getName 메서드가 호출.(메서드 오버라이드)
  2. 자바스크립트 엔진이 getName이라는 메서드를 찾는 방식은 가장 가까운 대상인 '자신'의 프로퍼티를 검색사고, 없으면 그 다음으로 가까운 대상인 __proto__를 검색하는 순서로 진행 됨.
  3. 즉, __proto__에 있는 메서드는 자신에게 있는 메서드보다 검색순서에서 밀려 호출되지 않는 것.

 

< 프로토 타입 체인 (prototype chain) >
: 어떤 데이터의 __proto__ 프로퍼티 내부에 다시 __proto__ 프로퍼티가 연쇄적으로 이어진 것.

  •  배열 리터럴의 __proto__를 열어보면 또 __proto__가 존재 하는데, 이는 prototype 자체가 '객체(Object)' 이기 때문.
  • 기본적으로 모든 객체의 __proto__에는 Object.prototype이 연결됨.
  • 그렇기 때문에 배열이 Array.prototype 내부의 메서드를 마치 자신의 것처럼 실행할 수 있는것. 그리고 마찬가지로 Object.prototype 내부의 메서드도실행할 수 있음 >> 생략가능한  __proto__ 를 한번 더 따라가가면 Object.prototype을 참조할 수 있기 때문.

데이터 타입별로 위와같은 삼각형을 그려 보았을 때, 위쪽 삼각형의 우측 꼭지점에는 무조건 Object.prototype이 존재하게 되며, 전체 프로토타입의 구조는 생성자 함수를 모두 따져보면 무한대의 삼각형 형태가 나올 수 있음. 하지만 우리는 일반적으로 인스턴스와 "직접적인 연관" 이 있는 삼각형에만 주목하여 파악하면 됨.

 

 

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

Object.prototype.getEntries = function() {
  let res = [];
  for (let prop in this) {
    if (this.hasOwnProperty(prop)) {
      res.push([prop, this[prop]]);
    }
  }
  return res;
};
let data = [
  ['object', { a: 1, b: 2, c: 3 }], //[["a",1], ["b",2],["c",3]]
  ['number', 345], // []
  ['string', 'abc'], //[["0","a"], ["1","b"], ["2","c"]]
  ['boolean', false], //[]
  ['func', function () {}], //[]
  ['array', [1, 2, 3]]
 // [["0", 1], ["1", 2], ["2", 3]]
  ];
data.forEach(function(datum) {
  console.log(datum[1].getEntries())
});
  • 어떤 생성자 함수이든 prototype은 반드시 객체(Object)이기 때문에 Object.prototype이 언제나 프로토타입 체인의 최상단에 존재하게 됨. 따라서 객체에서만 사용할 메서드는 다른 데이터 타입처럼 프로토타입 객체 내부에 정의할 수 없음.
  • 위의 예제에서 보았을 때, Object.prototype 내부에 getEntries라는 메서드를 할당하였고 이는 객체에서 사용하기 위한 메서드임.
  • 결과적으로, data라는 배열에 들어있는 원소들에 적용했을때 객체가 아닌 다른 데이터타입을 가지는 경우도 전부 오류없이 값을 반환한 것을 볼 수 있음.
  • 이는 어떤 데이터 타입이든 무조건 프로토타입 체이닝을 통해 getEntries 메서드에 접근할 수 있기 때문. 
  • 이러한 이유로 객체전용 메서드들은 Object.prototype이 아닌 Object에 Static 메서드로 부여할수밖에 없음.(Object에 직접 부여해야 함.)

  • object.create를 사용해 __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);
  1. 위의 코드에서 보았을때 _proto에는 __proto__프로퍼티가 없는 객체가 할당되었음.
  2. obj의 경우 _proto를 __proto__ 로 하는 객체가 할당되었음.
  3. obj를 출력해보면 __proto__에는 오직 getValue메서드만 존재하며, __proto__및 constructor 프로퍼티는 존재하지 않음. 
  4. 이 방식으로 만든 객체의 경우 내장메서드 및 프로퍼티가 제거되어 기본기능에 제약이 생기지만, 객체자체의 무게가 가벼워지기 때문에 성능상의 이점을 가짐.

 

< 다중 프로토타입 체인 >

  • 자바스크립트의 기본 내장 데이터 타입들은 모두 프로토타입 체인이 1-2 단계로 끝나는 경우만 있었지만, 사용자가 새롭게 만드는 경우 그 이상도 얼마든지 가능함.
  • 대각선의 __proto__를 연결해 나가기만 하면 무한대로 체인 관계를 만들 수 있는데, 이 방식으로 다른 언어의 클래스와 비슷하게 동작하는 구조를 만들 수 있다.
  • 대각선의 __proto__를 연결하는 방법은 __proto__가 가리키는 대상, 즉 생성자 함수의 prototype이 연결하고자 하는 상위 생성자 함수의 인스턴스를 바라보게끔 해주면 된다.
let Grade = function() {
  let args = Array.prototype.slice.call(arguments);
  for(let i = 0; i < args.length; i++) {
    this[i] = args[i];
  }
  this.length = args.length;
};
let g = new Grade(100, 80);
  • 변수 g는 Grade의 인스턴스를 바라본다. Grade의 인스턴스는 여러 개의 인자를 받아 각각 순서대로 인덱싱하여 저장하고 length 프로퍼티가 존재하는 등 배열의 형태를 지니지만 배열 메서드를 사용할 수 없는 유사배열객체이다.
  • 이 인스턴스에서 배열 메소드를 직접 쓸 수 있게끔 하고 싶다면 g.__proto__, 즉 Grade.prototype이 배열의 인스턴스를 바라보게 하면 된다.
Grade.prototype = [];
  • 이렇게 g 인스턴스는 프로토타입 체인에 따라 g 객체 자신이 지니는 멤버, Grade의 prototype에 있는 멤버, Array.prototype에 있는 멤버, 끝으로 Object.prototype에 있는 멤버에까지 모두 접근할 수 있게 된다.
admin