JS - 프로토타입 이해하기

코지클래식·2022년 7월 29일
0
post-thumbnail

누가 이런 질문을 올려주셨다.

1. 질문

   function grandfather() {}
   grandfather.prototype.name = '홍길동'
   
   function father(){}
   father.prototype = new grandfather(); 
   father.prototype.here = 'here father'

   function me(){}
   me.prototype = new father();
   me.prototype.here = 'here me'

console.dir(me) 에서 제가 생각하는 출력 값은 [[prototype]] : father 이 한번은 나오는 것 인데 이렇게 나오지 않고 [[prototype]]: grandfather이 두번 나옵니다. 왜 그런건가요?

원래 자바스크립트를 하지도 않지만, 왜 이런지 흥미가 생겨 원리를 찾아보게 되었다.

결론 2줄요약

각 prototype을 대표하는 (나타내는)값은 prototype.constructor (생성자함수)가 된다.
father의 prototype.construcotr도, me의 prototype.constructor도, 모두 grandfather() {}이 되기 때문에 prototype:grandfather이 2번 나타난다.


3. 동작 분석해보기

먼저 전체 코드를 분석하기 전에,
생성자함수(=f)/프로토타입 객체(=P)/인스턴스 객체(=I)의 관계에 대해 먼저 알고 넘어가자.

결론 요약

  1. 생성자함수.prototype = 프로토타입객체
  2. 프로토타입객체.constructor = 생성자함수
  3. 인스턴스객체.__proto__ =프로토타입 객체

이 점을 생각하면서, 아래의 코드에서 일어나는 일을= 분석해보자.

코드

function grandfather() {} // 요소1. 생성자 함수
grandfather.prototype.name = '홍길동' // 요소2. 프로토타입 객체
const g = new grandfather(); // 요소3. 인스턴스 객체

console.log(grandfather.prototype); // {name: 홍길동}
console.log(grandfather.prototype.constructor) // [Function: grandfather]
console.log(g.__proto__) // {name: 홍길동}
console.log(g.constructor) // [Function: grandfather]

동작 해설

  1. function grandfather() {} 선언

    • 생성자 함수 생성(=f) (**type = function)
    • 프로토타입 객체 생성(=P) (**type = object)
    • 생성자함수(=f).prototype = 프로토타입 객체(=P)
    • 프로토타입객체(=P).constructor = 생성자함수(=f)
  2. grandfather.prototype.name = 홍길동

    • 프로토타입 객체(=P)에 프로퍼티(어트리뷰트) name:홍길동 부여
      => P.name = '홍길동'
  3. const g = new gradnefather()

    • 인스턴스 객체 생성(=I) (**type = object)
    • 모든 인스턴스는 "[[Prototype]]"이라는 내부슬롯을 갖는다.
    • 이 prototype은 생성자함수(=f)의 프로토타입(=P)을 가리킨다.(=f.prototype=P)

추가 해설1

여기까지만 보면, f 와 P의 관계가 꽤 명확해 보인다.

여기서 명심해야 할 점은, I 와 P의 관계이다.
예를 들면, I.name을 호출할 때에는, 동작원리가 꽤 명확해 보인다.

  1. I에서 'name' 프로퍼티를 찾을 때에는, 먼저 자기 자신에서 찾는다.
  2. 없으면 I.prototype에 가서 찾게 된다.
  3. 따라서 I.name = I.prototype.name = P.name = '홍길동'이 된다.

추가해설2

그렇다면 I 의 생성자 함수(=f)를 찾는 것은 어떻게 하는 것일까?
바로 I.constructor가 가리키는 함수를 찾아가면 되는 것이다.
이 원리는 바로 위에서 name을 찾을 때와 똑같은 원리로 동작한다.
constructor 또한 I 자체에 선언된 것이 아니고, I.prototype.constructor를 찾는 일이다.

그래서 결국

그렇다면 function father()을 선언하고, father함수의 프로토타입을 지정하면 무슨일이 일어날까?

function grandfather() {} // 요소1. 생성자 함수
grandfather.prototype.name = '홍길동' // 요소2. 프로토타입 객체
// const g = new grandfather(); // 요소3. 인스턴스 객체

function father(){} // 요소4. 생성자함수 2
father.prototype = new grandfather();  // 요소5. 생성자함수2.프로토타입 = 요소3.인스턴스 객체

console.log(father.prototype) // grandfather {}
console.log(father.prototype.constructor) // [Function: grandfather])

추가해설3

  1. father.prototype은, grandfather() 인스턴스 객체를 가리키게 된다.
  2. father.prototype.constructor = grandfather.constructor = grandfather.prototype.constructor = grandfather() {} 이 된다.

이는 prototype 자체는 이름없는 객체라고 볼 수 있기 때문에, 생성자함수(constructor)를 가리키게 되는 상황에서 발생하는 일이라고 볼 수 있겠다.

따라서, father.prototype의 생성자 함수는 father()이 아닌 grandfather()이 된다.

추가 궁금증

1. 프로토타입에 선언한 프로퍼티 찾기

function grandfather() {} // 요소1. 생성자 함수
grandfather.prototype.name2 = '홍길동' // 요소2. 프로토타입 객체
const g = new grandfather(); // 요소3. 인스턴스 객체

function father(){}
father.prototype = new grandfather(); 
father.prototype.here = 'here father';
const f = new father();



console.log(grandfather.name2) 
// grandfather - 프로토타입에 선언한 프로퍼티는 생성자함수에 영향을 주지 않는다.

console.log(grandfather.here) 
// undefined - 인스턴스객체에 선언한 프로퍼티는 생성자함수에 영향을 주지 않는다.

console.log(g.name2)
// 홍길동 - 프로토타입에 선언한 프로퍼티는, 인스턴스 객체에 영향을 준다.

console.log(g.here) 
// 인스턴스 객체에 선언한 프로퍼티는 프로토타입객체에 영향을 주지 않는다.

console.log(father.name2) // father
console.log(father.here) // undefined
console.log(f.name2) // 홍길동
console.log(f.here) // fatherhere

프로토타입에 선언한 프로퍼티 name2/here은, 모두 생성자함수에서는 그 값을 찾지 못하고, 객체 인스턴스에서만 그 프로퍼티를 찾을 수 있는 것으로 보인다.

그렇다면, 생성자함수에 프로퍼티를 넣어주면, 이 친구들은 값을 어떻게 찾게될까?
재미로 테스트해봤다. 결과를 공유한다.

2. 생성자 함수에 선언한 프로퍼티 찾기

function grandfather() {
    this.age = 60
}

const g = new grandfather(); 


function father(){
    this.age2 = 30
}
father.prototype = new grandfather(); 
const f = new father();

console.log(grandfather.age) // undefined
console.log(grandfather.age2) // undefined
console.log(g.age) // 60
console.log(g.age2) // undefined

console.log(father.age) // undefined
console.log(father.age2) // undefined
console.log(f.age) // 60
console.log(f.age2) // 30

결론적으로 말하면

  1. 생성자함수 자체에는 값이 생성되지 않았고
  2. 인스턴스객체에는 값이 생성되었으며
  3. father()의 객체는, prototype인 grandfather 인스턴스에 age 가 있으므로, 그 값을 찾아서 반환했다.
profile
코지베어

0개의 댓글