TIL60.프로토타입(2)체인

조연정·2020년 12월 28일
0
post-thumbnail

프로토타입 2번째, '프로토타입 체인'에 대해서 알아보자.

Prototype chain

메서드 오버라이드

"오버라이드 = 덮어쓰기"

  • 생성자 함수를 이용하여 상속을 하다보면 물려받은 부모의 기능을 그대로 사용하지 않고 재정의해야 할 경우가 생긴다. 이때 자식클래스에서 부모클래스의 기능을 재정의할 때 사용하는 것이 '오버라이드' 이다.
  • 원본을 제거하고 다른 대상으로 교체하는 것이 아니라 원본이 그대로 있는 상태에서 다른 메서드를 덮어씌우는 것이다.
function Person(name) {
  this.name = name;
};
//getName 메서드 정의
Person.prototype.getName = function () {
  return this.name;
}

let hero = new Person('아이언맨');
//같은 이름의  getName 메서드 정의
hero.getName = function () {
  return `이름: ` + this.name;
};
console.log(hero.getName());

생성자 함수와 인스턴스가 같은 이름의 메서드를 가지면, 생성자의 메서드가 아닌 인스턴스의 메소드가 호출된다.

오버라이딩이 이뤄져 있는 상황에서 prototype에 있는 메소드에 접근하려면?

*자바스크립트 엔진이 getName이라는 메서드를 찾는 방식
: 자신의 프로퍼티 검색 → 그 다음으로 가까운 __proto__ 프로퍼티 검색

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

→ prototype에 name 프로퍼티가 없기때문에 undefined가 출력된다. 문제를 해결하기 위해선 prototype에 name 프로퍼티를 추가하면 된다.

Person.prototype.name = "몽이"
console.log(hero.__proto__.getName());
//몽이

→ 이제는 값을 출력하지만, 원하는 결과 ''아이언맨"이 출력되지 않았다. this가 protype이 아닌 인스턴스를 바라보도록 해주면 된다.

console.log(hero.__proto__.getName.call(hero));
//아이언맨

메서드 체인

  • 자바스크립트는 특정 객체의 프로퍼티나 메소드에 접근시 객체 자신의 것뿐 아니라 proto가 가리키는 링크를 따라서 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 접근할 수 있다.
  • 즉, 특정 객체의 프로퍼티나 메소드 접근시 만약 현재 객체의 해당 프로퍼티가 존재하지 않는다면 proto가 가리키는 링크를 따라 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 차례로 검색하는 것이 바로 프로토타입 체인이다.
  • 모든 프로토타입 체이닝의 종점은 최상위 객체인 Object.prototype이다. 즉, 모든 프로토타입 체인의 끝은 항상 Object.prototype이고, 따라서 Object.prototype은 proto속성이 없다.
  • 하위 객체는 상위 객체의 프로퍼티나 메소드를 상속받는 것이 아니라 공유한다.

메서드 체인

  • 자바스크립트는 특정 객체의 프로퍼티나 메소드에 접근시 객체 자신의 것뿐 아니라 proto가 가리키는 링크를 따라서 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 접근할 수 있다.
  • 즉, 특정 객체의 프로퍼티나 메소드 접근시 만약 현재 객체의 해당 프로퍼티가 존재하지 않는다면 proto가 가리키는 링크를 따라 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 차례로 검색하는 것이 바로 프로토타입 체인이다.
  • 모든 프로토타입 체이닝의 종점은 최상위 객체인 Object.prototype이다. 즉, 모든 프로토타입 체인의 끝은 항상 Object.prototype이고, 따라서 Object.prototype은 proto속성이 없다.
  • 하위 객체는 상위 객체의 프로퍼티나 메소드를 상속받는 것이 아니라 공유한다.
  • 해당 객체에 없는 프로퍼티나 메소드를 접근할 때 프로토타입 체이닝이 일어난다.
var a = {
    attr1: 'a'
};

var b = {
    __proto__: a,
    attr2: 'b'
};

var c = {
    __proto__: b,
    attr3: 'c'
};

console.log(c.attr1)
// a

1)자바스크립트 엔진

c객체 내부에 attr1 속성을 찾는다.(x) → 찾는 속성이 없다면, c객체에 __proto__속성이 있는지 확인한다.(o) → c객체의 __proto__속성이 참조하는 객체로 이동한다. → b객체의 내부에 attr1속성을 찾는다.(x) → b객체에 proto 속성이 존재하는지 확인한다(o) → b객체의 __ proto__ 속성이 참조하는 객체로 이동한다. → a객체 내부에 attr1속성을 찾는다(o)→ 찾은 속성의 값을 리턴한다.

2)어떤 객체에도 존재하지 않는 속성을 찾게 되는 경우의 엔진

a객체 내부에 원하는 속성을 찾는다.(x) → a객체에 proto속성이 있는지 확인한다.(o) → a객체의 proto속성이 참조하는 객체로 이동한다. → Object.prototype로 이동 후, 원하는 속성을 찾는다. (x) → Object.prototype에서 proto를 찾는다.(x) → undefined 리턴

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

앞서 말했듯이, Object.prototype 은 언제나 프로토타입 체인의 최상단에 존재한다.
따라서 객체메서드(ex. hasOwnProperty, toString, ...)는 프로토타입 객체 안에 정의할 수 없다. 객체에서만 사용할 메서드를 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())
});

위 예제에서 객체에만 사용할 의도로 'getEntries' 라는 메서드를 만들었다.forEach로 data의 각 줄마다 getEntries를 실행했더니, 모든 데이터가 오류 없이 결과를 반환하고 있다. 원래 의도대로라면 객체가 아닌 타입(number, string, array)에 대해서는 오류를 반환해야하지만, 프로토타입 체이닝을 통해 getEntries 메서드에 접근할 수 있어서 그렇게 동작하지 않는 것이다. 이와 같은 이유로 객체만을 대상으로 동작하는 객체 전용 메서드들은 Object.prototype이 아닌 Static Method 로 부여한다.

*static method: 자바스크립트의 클래스에서 prototype에 할당되지 않고, 클래스 자체에 할당된 함수를 static 메소드라고 한다. 클래스 자체에 할당되었기 때문에 클래스의 인스턴스를 통해서는 호출될 수 없으며 클래스를 통해 호출해야 한다. 클래스가 가지고 있지만 클래스의 인스턴스에 바인딩되지 않은 기능을 구현하고자 할때 사용된다.

다중 프로토타입 체인

자바스크립트의 기본 내장 데이터타입들은 모두 프로토타입 체인이 1단계이거나 2단계로 끝나는 경우만 있었지만, 사용자가 새롭게 만드는 경우에는 그 이상도 가능하다.대각선의 proto를 연결해나간다면 무한대로 체인 관계를 이어나갈 수 있다.

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);

console.log(g)
//Grade { '0': 100, '1': 80, length: 2 }
console.log(g.pop())
//TypeError: g.pop is not a function
=> 유사배열객체이기 때문에 배열메소드 pop을 사용할 수 없다.

변수 g는 Grade의 인스턴스를 바라본다. Grade의 인스턴스는 여러개의 인자를 받아 각각 순서대로 인덱싱해서 저장하고, length프로퍼티가 존재하는 배열의 형태를 지니지만, 배열의 메소드를 사용할 수 없는 *유사배열객체이다.

(*유사배열객체: 배열의 형태를 띄고있지만, 배열이 아닌 것을 유사객체라고 부른다. 예제코드에서 사용된 arguments 역시 유사배열로 되어있다.

*arguments: 함수의 인자갯수가 정해져있지 않을 때 사용하는 가변인자이다. 함수내에서 특별히 선언하지 않아도 그냥 사용할 수 있다.)

//기존
let g = new Grade(100, 80);

//변경
Grade.prototype = [];
let g = new Grade(100, 80);

배열 메서드가 가능하게 하려면 grade.prototype이 배열의 인스탄스를 바로보게 하면 된다.
이렇게되면, g인스탄스의 입장에서는 프로토타입 체인에 따라 g객체 자신이 지니는 멤버, Grade의 prototype에 있는 멤버, Array.prototype에 있는 멤버, 끝으로 Object.prototype에 있는 멤버까지 접근가능하다.

profile
Lv.1🌷

0개의 댓글