prototype vs __proto__

최초 업로드 2023-12-01 / 마지막 수정 2023-12-14

JS는 모든 객체에서 __proto__ 혹은 Object.getPrototypeOf[[Prototype]]내부 슬롯을 볼 수 있다. __proto__와는 사뭇 다른 prototype은 함수 객체에만 존재한다.

  • 함수의 .prototype는 본인이 new로 생성할 객체의 프로토타입을 반환하고
  • 함수의 .__proto__는 따로 설정해주지 않으면 언제나 Function.prototype을 반환한다.

고로, 어떤 함수 객체가 생성할 객체의 프로토타입 체인을 보고싶으면 __proto__가 아닌 prototype을 호출해야한다.

자세히 생각해보면 자연스러운 흐름이다. 보통 프로토타입 객체는 constructor를 가지고 있는 객체이다. constructor필드는 해당 프로토타입을 기반으로 객체를 만들면 만들어지는 것을 보여주는 공장 라인과도 같다. 그렇다면 함수 객체의 프로토타입은 반드시 함수를 만드는 constructor를 가지고 있어야하며, 함수 객체의 __proto__Function.prototype이 되는 것이 매우 자연스럽다는 것을 보여준다. 함수에 __proto__만 있고 prototype이 없다면 함수 객체가 만들어내는 객체의 프로토타입 체인을 확인할 길이 없으니, prototype이 존재하는 것 또한 자연스러워진다.

아래 예시는 두 개의 차이점을 직접 보기 위해 만들어보았다.


// create Animal
function Animal(hp) {
  this.alive = true;
  this.hp = hp;
}

Animal.prototype.eat = (food) => {
  // arrow 함수는 [[Contructor]]가 없기에 메서드로 사용된다는 것을 강조한다.
  this.hp += food;
};

// create Dog
function Dog(hp, breed) {
  Animal.call(this, hp);
  this.abilities = ["bark", "sniff"];
  this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// create GermanShepherd
function GermanShepherd(hp, strength) {
  Dog.call(this, hp, "German Shepherd");
  this.strength = strength;
}
GermanShepherd.prototype = Object.create(Dog.prototype);
GermanShepherd.prototype.constructor = GermanShepherd;

// test objects
const dog1 = new Dog(100, "a");
const gs1 = new GermanShepherd(100, 100);

// structure check
console.log(GermanShepherd.__proto__ === Function.prototype); // Function.prototype
console.log(GermanShepherd.__proto__.__proto__ === Object.prototype); // Object.prototype
console.log(
  gs1.__proto__, // Dog { constructor: [Function: GermanShepherd] }
  gs1.__proto__.__proto__, // Animal { constructor: [Function: Dog] }
  gs1.__proto__.__proto__.__proto__, // { eat: [Function (anonymous)] }
  gs1.__proto__.__proto__.__proto__.__proto__ // [Object: null prototype] {}
);
console.log(
  GermanShepherd.prototype, // Dog { constructor: [Function: GermanShepherd] }
  GermanShepherd.prototype.__proto__, // Animal { constructor: [Function: Dog] }
  GermanShepherd.prototype.__proto__.__proto__, // { eat: [Function (anonymous)] }
  GermanShepherd.prototype.__proto__.__proto__.__proto__ // [Object: null prototype] {}
);

next_js_caching_flow 그림 1. 코드를 정리한 그래프

함수 객체의 __proto__는 수동으로 설정해주지 않는 이상 언제나 Function.prototype을 가리키고 있다는 사실을 명심하자.

클래스의 경우

위의 방식도 pseudo-classical inheritance이고, class로 구현하는 것도 pseudo-classical inheritance라고 한다. 근데 구현에 따라서 프로토타입 체인 갯수가 다르다.

클래스는 프로토타입 체인이 두 개이고, 위 방식의 함수들로 만든 pseudo-classical inheritance는 프로토타입 체인이 하나이다.


수정 사항

2023-12-14: 이틀전 면접 보면서 pseudo-classical inheritance를 내가 제대로 알고 있는게 맞나 끝나고 확인해봤는데, 잘못알고 있어서 글을 대폭 수정함.

prototype vs __proto__

최초 업로드 2023-12-01 / 마지막 수정 2023-12-14

JS는 모든 객체에서 __proto__ 혹은 Object.getPrototypeOf[[Prototype]]내부 슬롯을 볼 수 있다. __proto__와는 사뭇 다른 prototype은 함수 객체에만 존재한다.

  • 함수의 .prototype는 본인이 new로 생성할 객체의 프로토타입을 반환하고
  • 함수의 .__proto__는 따로 설정해주지 않으면 언제나 Function.prototype을 반환한다.

고로, 어떤 함수 객체가 생성할 객체의 프로토타입 체인을 보고싶으면 __proto__가 아닌 prototype을 호출해야한다.

자세히 생각해보면 자연스러운 흐름이다. 보통 프로토타입 객체는 constructor를 가지고 있는 객체이다. constructor필드는 해당 프로토타입을 기반으로 객체를 만들면 만들어지는 것을 보여주는 공장 라인과도 같다. 그렇다면 함수 객체의 프로토타입은 반드시 함수를 만드는 constructor를 가지고 있어야하며, 함수 객체의 __proto__Function.prototype이 되는 것이 매우 자연스럽다는 것을 보여준다. 함수에 __proto__만 있고 prototype이 없다면 함수 객체가 만들어내는 객체의 프로토타입 체인을 확인할 길이 없으니, prototype이 존재하는 것 또한 자연스러워진다.

아래 예시는 두 개의 차이점을 직접 보기 위해 만들어보았다.


// create Animal
function Animal(hp) {
  this.alive = true;
  this.hp = hp;
}

Animal.prototype.eat = (food) => {
  // arrow 함수는 [[Contructor]]가 없기에 메서드로 사용된다는 것을 강조한다.
  this.hp += food;
};

// create Dog
function Dog(hp, breed) {
  Animal.call(this, hp);
  this.abilities = ["bark", "sniff"];
  this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// create GermanShepherd
function GermanShepherd(hp, strength) {
  Dog.call(this, hp, "German Shepherd");
  this.strength = strength;
}
GermanShepherd.prototype = Object.create(Dog.prototype);
GermanShepherd.prototype.constructor = GermanShepherd;

// test objects
const dog1 = new Dog(100, "a");
const gs1 = new GermanShepherd(100, 100);

// structure check
console.log(GermanShepherd.__proto__ === Function.prototype); // Function.prototype
console.log(GermanShepherd.__proto__.__proto__ === Object.prototype); // Object.prototype
console.log(
  gs1.__proto__, // Dog { constructor: [Function: GermanShepherd] }
  gs1.__proto__.__proto__, // Animal { constructor: [Function: Dog] }
  gs1.__proto__.__proto__.__proto__, // { eat: [Function (anonymous)] }
  gs1.__proto__.__proto__.__proto__.__proto__ // [Object: null prototype] {}
);
console.log(
  GermanShepherd.prototype, // Dog { constructor: [Function: GermanShepherd] }
  GermanShepherd.prototype.__proto__, // Animal { constructor: [Function: Dog] }
  GermanShepherd.prototype.__proto__.__proto__, // { eat: [Function (anonymous)] }
  GermanShepherd.prototype.__proto__.__proto__.__proto__ // [Object: null prototype] {}
);

next_js_caching_flow 그림 1. 코드를 정리한 그래프

함수 객체의 __proto__는 수동으로 설정해주지 않는 이상 언제나 Function.prototype을 가리키고 있다는 사실을 명심하자.

클래스의 경우

위의 방식도 pseudo-classical inheritance이고, class로 구현하는 것도 pseudo-classical inheritance라고 한다. 근데 구현에 따라서 프로토타입 체인 갯수가 다르다.

클래스는 프로토타입 체인이 두 개이고, 위 방식의 함수들로 만든 pseudo-classical inheritance는 프로토타입 체인이 하나이다.


수정 사항

2023-12-14: 이틀전 면접 보면서 pseudo-classical inheritance를 내가 제대로 알고 있는게 맞나 끝나고 확인해봤는데, 잘못알고 있어서 글을 대폭 수정함.

Copyright © 2023 Seho Lee All Rights Reserved.
</>
Latest Commit
d8c114a6-0bf3-5e24-9645-a55f1bd717ac
seho0808
2024-10-01T10:45:01Z