개발공부/자바스크립트

[JavaScript] 스터디 9일차_ 프로토타입(2) 체인

햄❤️ 2021. 7. 12. 20:45
728x90

2.1 메서드 오버라이드

 - prototype 객체를 참조하는 __proto__를 생략하면 인스턴스는 prototype에 정의된 프로퍼티나 메서드를 마치 자신의 것처럼 사용할 수 있다. 만약 인스턴스가 동일한 이름의 프로퍼티 또는 메서드를 가지고 있다면 원본이 그대로 있는 상태에서 다른 대상을 그 위에 얹는다(덮어씌운다). 이것이 메서드 오버라이드!

- 자바스크립트 엔진이 getName이라는 메서드를 찾는 방식은 가장 가까운 대상인 자기 자신의 프로퍼티를 검색하고, 없으면 그 다음 가까운 대상인 __proto__를 검색하는 순서로 진행된다. __proto__에 있는 메서드는 검색 순서에서 밀려 호출되지 않는다.

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()); // 바로 지금

 

 - this가 prototype 객체(iu.__proto__)를 가리키는데 prototype 상에는 name이라는 프로퍼티가 없어 undefined가 출력된다.  

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

 

 - 아래와 같이 prototype에 name 프로퍼티를 직접 정의해주면 그 값을 출력한다. 

Person.prototype.name = "이지금";
console.log(iu.__proto__.getName()); //이지금;

 

 - 메서드 오버라이딩 상황에서 prototype에 있는 메서드에 접근하려면, this가 인스턴스를 바라보도록 call or apply를 사용한다. 이런식으로 우회적으로 접근이 가능하다.  

console.log(iu.__proto__.getName.call(iu));
//this가 iu로 설정되어 '지금' 출력

 

2.2 프로토타입 체인

console.dir([1,2]);

 -  __proto__안에는 또 다른 __proto__가 존재한다. 이는 prototype 객체가 바로 객체이기 때문이다.
     기본적으로 모든 객체의 __proto__ 에는 Object.prototype이 연결된다. 

 

 

  -  __proto__는 생략이 가능하기 때문에, 배열이 Array.prototype 내부 메서드를 실행할 수 있는 것처럼 마찬가지로 Object.prototype 내부의 메서드도 자기 것처럼 실행할 수 있다. __proto__를 한번 더 따라가면 Object.prototype을 참조할 수 있기 때문! 

 

 ⭐  어떤 데이터의 __proto__ 프로퍼티 내부에서 다시 __proto__ 프로퍼티가 연쇄적으로 이어진것을 프로토타입 체인(prototype chain)이라 하고,
 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝(prototype chaining) 이라고 한다.

 

 2.1.2 메서드 오버라이드와 프로토타입 체이닝 예제_1번

  - arr변수는 배열이므로 arr.__proto__는 Array.prototype을 참조하고, Array.prototype.__proto__는 Object.prototype을 참조한다. 

let arr = [1, 2];
Array.prototype.toString.call(arr); // 1, 2
Object.prototype.toString.call(arr); // [object Array]
arr.toString();  // 1, 2 
// === arr.(__proto__).toString 

//메서드 오버라이딩
arr.toString = function() {
  return this.join('_');
};
arr.toString(); // 1_2

 

  - 생성자 함수는 모두 함수이기 때문에 Function 생성자 함수의 prototype과 연결된다. Function 생성자 함수 역시 함수이므로 다시 Function 생성자함수의 prototype과 연결된다. 이런식으로 __proto__의 constructor의 __proto__의 constructor... 를 재귀적으로 반복하는 루트를 따르면 끝없이 찾아갈 수 있다.
  but 우리는 인스턴스와 직접적인 연관이 있는 삼각형에만 주목한다. 

 

 

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

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

 

2.3.1 Object.prototype에 추가한 메서드에서의 접근

  - 객체에서만 사용할 용도의 getEntries 라는 메서드의 forEach 결과값은 모든 데이터가 오류없이 결과를 반환하는 것을 볼 수 있다. 어떤 데이터 타입이건 무조건 프로토타입 체이닝을 통해 getEntries 메서드에 접근 가능하기 때문

  - 따라서 객체만을 대상으로 동작하는 객체 전용 메서드들은 Object.prototype이 아닌 Object에 스태틱 메서드(static method)로 부여해야한다. 또한 생성자 함수인 Object와 인스턴스인 객체 리터럴 사이에는 this를 통한 연결이 불가능하기 때문에 여느 전용 메서드처럼 메서드명 앞의 대상이 곧 this가 되는 방식 대신 this 사용을 포기하고 대상 인스턴스를 인자로 직접 주입해야하는 방식으로 구현되어있다. 

 

🧡 static method(스태틱 메소드)?

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

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

 

 

2.4 다중 프로토타입 체인

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

대각선의 __proto__를 연결하는 방법은 __proto__가 가리키는 대상인 생성자 함수의 prototype이 연결하고자 하는 상위 생성자 함수의 인스턴스를 바라보게끔 해주면 된다. 

 

2.4.1 Grade 생성자 함수와 인스턴스 예제_1번

 - 변수 g는 Grade의 인스턴스를 바라본다. Grade의 인스턴스는 여러개의 인자를 받아 각 순서대로 인덱싱하여 저장하고, length 프로퍼티가 존재하는 등 *유사배열객체이다(배열 메서드는 사용 불가) 

 - 인스턴스에서 배열 메서드를 직접 쓰기 위해서는 g.__proto__ = Grade.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);

 

 - Array(생성자 함수)의 인스턴스 = [], 즉 배열의 인스턴스 [] 로 Grade.prototype를 할당해준다. 이렇게 되면 Grade의 인스턴스인 g에서 배열 메서드를 사용할 수 있다. 

Grade.prototype = [];

 

 

 - g 인스턴스의 입장에서는 프로토타입 체인에 따라 g객체 자신이 지니는 멤버, Grade의 prototype에 있는 멤버, Array.prototype에 있는 멤버, Object.prototype에 있는 멤버까지 접근할 수 있다. 

 

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]

 

 

정리

 ✨ 직각삼각형의 대각선 방향, 즉__proto__방향을 계속 찾아가며 최종적으로는 Object.prototype에 당도하게 된다. 이런식으로__proto__안에 다시__proto__를 찾아가는 과정을 프로토타입 체이닝이라고 하며, 이 프로토타입 체이닝을 통해 각 프로토타입 메서드를 자신의 것처럼 호출 할 수 있다. 이때 접근 방식은 자신으로부터 가장 가까운 대상부터 점차 먼 대상으로 나아가며, 원하는 값을 찾으면 검색을 중단한다.

✨Object.prototype에는 모든 데이터 타입에서 사용할 수 있는 범용적인 메서드만 존재하며, 객체 전용 메서드는 Object. 생성자 함수에 스태틱하게 담겨있다. 

 

 

참고 자료

정재남, 『코어자바스크립트』, 위키북스(2019)

https://heo-dev-0229.tistory.com/44

728x90