2. 프로토타입 체인

 1) 메서드 오버라이드

  - __proto__는 생략 가능한 프로퍼티이므로 __proto__를 생략하면 인스턴스는 생성자 함수의 prototype에 정의된 프로퍼티나 메서드를 자기 것처럼 사용 가능

  - 이때, 인스턴스가 통일한 이름의 프로퍼티나 메서드를 가진다면?

// 생성자 함수 Person과 Person의 prototype으로 getName을 생성
var Person = function(name) {
  this.name = name;
};
Person.prototype.getName = function() {
  return this.name;
};

// 생성자 함수 Person에 의해 만들어진 인스턴스인 iu와 iu의 메서드로 getName 생성
var iu = new Person('지금');
iu.getName = function() {
  return '바로' + this.name;
};

// iu.getName을 했을 때
// iu 객체의 생성자인 Person의 prototype을 참조하는 iu.__proto__.getName과 iu의 자체 메서드인 iu.getName중
// iu의 자체 메서드인 iu.getName이 출력됨
console.log(iu.getName());   // 바로 지금

    → 자바스크립트 엔진에서 getName이라는 메서드를 찾는 방식은 가장 가까운 대상인 자신의 프로퍼티를 먼저 탐색

    → 자신의 프로퍼티 중 getName이 없으면 그 다음으로 가까운 대상인 __proto__를 검색하는 순서로 진행

    → iu객체에는 자신의 프로퍼티 중 getName이라는 메서드가 있으므로 바로 출력, 이 현상이 메서드 오버라이드

    → 메서드 오버라이드는 iu의 자체 메서드가 있을 때, __proto__의 메서드가 없어지고 교체되는 것이 아닌 덮어씌워지는 것

    → 따라서, __proto__의 getName 메서드도 호출할 수 있음

// __proto__를 명시해주기
// iu.__proto__가 this 객체가 되었는데, iu.__proto__에 name 객체가 없어 undefined 되었지만
// getName이 iu의 자체 메서드가 아닌 iu.__proto__의 메서드로 인식되었음을 확인할 수 있음
console.log(iu.__proto__.getName())  // undefined

// Person.prototype에 name 프로퍼티를 지정해주면
// iu.__proto__가 이를 참조하여 iu.__proto__에도 같은 값을 갖는 name프로퍼티가 생성됨
// 따라서, 이번에는 getName의 this 객체가 iu.__proto__가 되고 this.name은 iu.__proto__의 name 프로퍼티로 잘 인식됨
Person.prototype.name = '이지금';
console.log(iu.__proto__.getName());   // 이지금

// call 메서드를 사용하면 this가 바라보는 것을 Person의 prototype에서 iu 인스턴스 자체로 바꿔줄 수 있음
// iu 인스턴스를 생성할 때, 생성자 함수 Person에 '지금'을 넣으며 생성했고 따라서 iu 인스턴스의 자체 name은 '지금'
console.log(iu.__proto__.getName.call(iu));  // 지금

 

 

 2) 프로토타입 체인

  - 배열 [1, 2]의 디렉터리 구조를 보면 [[prototype]] 내에 또 하나의 [[prototype]]이 존재

  - prototype 객체가 '객체'이기 때문에 '객체(Object)'의 prototype을 참조하기 때문

Object의 prototype을 참조하는 배열의 prototype 도식화

  - 따라서, 배열 [1, 2]는 배열(Array)의 prototype내의 메서드도 사용 가능하고, 객체(Object)의 prototype 내의 메서드도 사용 가능

  - 이처럼 어떤 데이터의 __proto__ 프로퍼티 내부에 다시 __proto__프로퍼티가 연쇄적으로 이어진 것이 프로토타입 체인

  - 프로토타입 체인을 따라가며 검색하는 것이 프로토타입 체이닝

  - 프로토타입 체이닝은 메서드 오버라이드와 비슷한 과정으로 검색함

    → 메서드를 호출했을 때, 자신의 __proto__에 해당 메서드가 없다면 그 위의 __proto__에서 메서드 검색

var arr = [1, 2];

// 1)
// arr을 Array의 prototype 내의 메서드 toString으로 불러왔을 때
Array.prototype.toString.call(arr);   // 1 2
// arr을 Object의 prototype 내의 메서드 toString으로 불러왔을 때
Object.prototype.toString.call(arr);  // [object Array]
// 2)
// arr 자체 메서드가 없을 때 toString 검색과정
// arr 자체 메서드에서 toString을 검색 → 없음
// arr 자체 메서드 위에 있는 Array의 prototype 내에서 toString 메서드 검색 → 있음 → 사용
arr.toString();   // 1 2

// arr 자체 메서드로서 toString 생성
arr.toString = function () {
  return this.join('_');
};

// arr 자체 메서드가 있을 때 toString 검색과정
// arr 자체 메서드에서 toString을 검색 → 있음 → 사용
arr.toString();   // 1_2

  - 위의 경우처럼 Array 뿐아니라, Number, String, Boolean 등 자바스크립트 데이터는 모두 그 위에 Object의 prototype을 참조함

 

 

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

  - 객체(Object) 내부에 메서드를 정의하면 다른 모든 자바스크립트 데이터들은 이 메서드를 사용할 수 있음

    → 따라서 객체에서만 사용할 메서드는 객체(Object)의 prototype 내에 정의할 수 없음

Object.prototype.getEntries = function() {
  var res = [];
  for (var prop in this) {
    if (this.hasOwnProperty(prop)) {
      res.push([prop, this[prop]]);
    }
  }
  return res;
};

var 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());
});

    → 객체(Object)의 메서드 getEntries는 hasOwnProperty를 사용하여 객체가 특정 프로퍼티(prop)를 자신만 소유하는 지 확인

    → hasOwnProperty를 this에 대해 실행하여 객체인지 아닌지 판단하고, 객체인 것에 대해서만 출력하려고 함

    → 모든 데이터가 객체를 참조하여, getEntries를 자신의 것처럼 사용하여, getentries의 this가 Object로 고정되지 않고, 매 데이터마다 해당 데이터로 바뀜

    → 따라서, 객체에서만 동작하는 객체 전용 메서드는 Object.prototype이 아닌, 스태틱 메서드로 부여해야함

    → 또한, Object와 인스턴스 사이의 this 연결이 안되므로 this를 그냥 사용하면 메서드명 앞의 객체가 this가 되는 것 대신에 this 사용을 포기하고 인스턴스 자체를 인자로 사용해야함

    → ex) Object.frees(instance) 대신, instance.freeze() / Object.getPrototypeOf(instance) 대신, instance.getPrototype()

 

  - 프로토타입의 가장 위에는 항상 Object.prototype이 있어 참조할 수 있지만, 예외적으로 Object.prototype에 접근할 수 없도록 할 수 있음

var _proto = Object.create(null)
_proto.getValue = function(key) {
    return this[key];
};
var obj = Object.create(_proto);
obj.a = 1;
console.log(obj.getValue('a'));
console.dir(obj);

    → Object.create(null)은 __proto__가 없는 객체를 생성하여, Object.prototype을 참조하지 않음

    → 따라서, 디렉터리 구조를 확인한 결과, Object.create(null)로 생성된 _proto내부에는, 직접 생성한 getValue 메서드 이외에 다른 메서드는 없음(Object.prototype을 참조하지 않아서 Object의 메서드를 사용할 수 없음)

    → 기본 기능에 제약이 생기지만, 객체의 무게가 가벼워짐

 

 

 4) 다중 프로토타입 체인

  - 프로토타입 체인은 보통 1단계(객체) - 2단계(나머지)로 끝나지만, __proto__를 연결해나가기만 하면 무한대로 체인관계를 이어갈 수 있음

  - 이를 이용해 다른 언어의 클래스롸 비슷하게 동작하는 구조 생성 가능

  - __proto__를 연결하는 방법은 __proto__가 가리키는 대상(생성자 함수의 prototype)이 연결하고자하는 상위 생성자 함수의 인스턴스를 바라보게끔 하면 됨

// Grade는 여러 인자를 받아서, 순서대로 인덱싱하여 저장하고, length 프로퍼티도 부여
// 배열의 형태로 저장하지만 배열(Array)의 메서드를 사용하지는 못하는 유사 배열 객체
var Grade = function () {
  var args = Array.prototype.slice.call(arguments);
  for (var i = 0; i < args.length; i++) {
    this[i] = args[i];
  }
  this.length = args.length;
};

var g = new Grade(100, 80);

    → g는 Grade의 인스턴스

    → Grade는 배열의 형태로 저장하지만 배열(Array)의 메서드를 사용할 수는 없는 상태

    → Grade가 배열의 메서드를 사용할 수 있도록, Grade.prototype이 Array를 참조할 수 있도록 해야함

Grade.prototype = [];

    → 위의 코드를 작성하면 Grade.prototype이 (대괄호로 감싸진)배열의 형태를 갖게되며, Array의 프로토타입 삼각형에 연결됨

Array를 참조할 수 있게 된 Grade 도식화

    → 이에 따라서, Grade로 생성된 g 또한 Array의 메서드를 사용할 수 있게 됨

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

g.pop();            // 배열의 끝 값을 없애며 다른 데 저장하는 배열 메서드 pop() 적용 가능
console.log(g);  // Grade(1) [100]

g.push(90);       // 배열의 새로운 값을 저장하는 배열 메서드 push() 적용 가능
console.log(g);  // Grade(2) [100, 90]

+ Recent posts