[javascript] __proto__ 그 끝은 어디에?

류태오·2022년 6월 18일
3

Front-end

목록 보기
10/13
post-thumbnail
post-custom-banner

자바스크립트 상속

자바스크립트 상속은 아는 개념이라 강의내용만 듣고 넘어가려다가 예전에 개발할 때 헷갈리는 부분이 있어서 잘 안쓰다보니 이번에는 대충 넘어가지 않고 집요하게 정리를 해보았다.

1. 상속

01. function방식과 class방식의 차이

ES6 도입 이전에도 function을 통해서 생성자를 만들어 클래스처럼 사용하였으나, class가 나오면서 기능이 더 명확해졌다.

function AnimalFunc(type, name, sound) {
  this.type = type;
  this.name = name;
  this.sound = sound;
  this.say = function () {
    console.log(this.sound);
  };
}AnimalFunc.prototype.noise = function () {
  console.log(this.sound);
};// -----------------------------------------
class AnimalClass {
  constructor(type, namename, sound) {
    this.type = type;
    this.namename = namename;
    this.sound = sound;
  }
  say() {
    console.log(this.sound);
  }
}AnimalClass.prototype.noise = function () {
  console.log(this.sound);
};

같은 생성자를 Function방식과 Class방식으로 짜보았다.

console.log(AnimalFunc.prototype); // {noise: f()}
console.log(AnimalFunc.prototype.say); // undefined
console.log(AnimalFunc.prototype.noise); // f() {}
ㅤ
console.log(AnimalClass.prototype); // {noise: f()}
console.log(AnimalClass.prototype.say); // f say() {} //?????
console.log(AnimalClass.prototype.noise); // f() {}

의문 1.
여기서 의문인 점은 클래스로 짠 say 함수의 경우 직접 콘솔에 입력하면 나오지만 prototype으로 찍으면 안나오는가?
...결국 답을 찾지 못했다.


function Animal(type, name, sound) {
  this.type = type;
  this.name = name;
  this.sound = sound;
}Animal.prototype.say = function () {
  console.log("say", this.sound);
};// 개로 상속 --------------------------------
function Dog(name, sound) {
  Animal.call(this, "개", name, sound); // call 함수
}Dog.prototype = Animal.prototype;Dog.prototype.speak = function () {
  console.log("speak", this.sound);
};// 고양이로 상속 --------------------------------
class Cat extends Animal {
  constructor(name, sound) {
    super("고양이", name, sound); // super 함수
  }
}Cat.prototype.speak = function () {
  console.log("speak", this.sound);
};// 객체 생성 --------------------------------
const mong = new Dog("몽이", "왈왈");
const mio = new Cat("미오", "냥냥");

의문 2.
완전 다른 방식인데 왜 같은 것처럼 설명되어 있을까?


Dog.prototype = Animal.prototype;을 하게 되면 말 그대로 Animal.prototype을 함께 쓰게 되기 때문에 다른 동물들도 같은 방식으로 연결하게 되면 Dog에 업데이트하든 Pig를 업데이트하든 Animal.prototype안에 다 들어가게 된다. 상속이 아니라 프로토타입 공유에 가까운..

반면 오른쪽처럼 class로 상속받은 Cat의 경우 Animal.prototype만 상속받으면서 추가되는 메서드는 자신에게만 추가한다.


02. prototype과 __proto__

세 가지 키워드를 통해서 상속받는 관계를 추적하고 접근할 수 있게 된다.

    1. prototype : 해당 클래스의 prototype에 접근
    1. __proto__ : 상속받은 곳으로 참조
    1. constructor : 해당 인스턴스를 만든 생성자를 참조
console.log(Animal.prototype); // {say: ƒ (), speak: ƒ ()}
console.log(Dog.__proto__); // ƒ ()
console.log(Cat.__proto__); // ƒ Animal() {}//------------------------------------
console.log(Dog.prototype); // {say: ƒ (), speak: ƒ ()}
console.log(mong.__proto__); // {say: ƒ (), speak: ƒ ()}
ㅤ
console.log(Cat.prototype); // Animal {speak: ƒ (), say: ƒ (), constructor: Object}
console.log(mio.__proto__); // Animal {speak: ƒ (), say: ƒ (), constructor: Object}//------------------------------------
console.log(mong.__proto__.__proto__ === Animal.prototype); // false
console.log(mio.__proto__.__proto__ === Animal.prototype); // true

이것을 보면 function 방식으로 선언한 Dog의 경우 기능적으로만 상속처럼 보일 뿐 실제로 구조적으로 연결되는 것은 아니다. Dog.__proto__ 는 빈 함수를 가리키고 있는 반면, Cat.__proto__ 는 Animal()을 정확하게 가리키고 있다.

여기서 하나 의문이었던 것이
mio.__proto__.__proto__Cat.__proto__ 가 콘솔에서 같지 않게 나왔다는 것이었다. __proto__는 거슬러 올라가는 것이 아닌가...? 사실 이 때 살짝 포기했다

---의미
⬇️ constructor
mio.constructor===Cat같다
Cat.constructor===Animal.constructor같다?
⬇️ 여기까지는 괜찮다.
mio.__proto__===Cat.prototypetrue
mio.__proto__.__proto__===Animal.prototypetrue
⬇️ ....?
mio.__proto__.__proto__!==Cat.__proto__같지 않다???
Cat.__proto__!==Animal.prototype같지 않다???
⬇️ ....
Cat.prototype.__proto__===Animal.prototypetrue

무슨 끈기가 생겼는지.. 고생끝에 드디어 이해했다.
콘솔로 하나하나 확인해보다가 겨우 깨닫게 되었다.
__proto__는 "인스턴스"에서 거슬러올라 갈 때만 해당되는 것
아니 그림으로 그려서 확인하는 것이 더 좋겠다.

prototype__proto__는 서로 반대되는 개념도 아니고, 일직선의 구조가 아니기 때문에 의도한대로 되지 않았던 것이다.

핵심은 여기에 있다.
세 가지 키워드의 관계를 그림으로 나타냈다.

인스턴스를 기준으로 자신을 찍어낸 클래스를 찾으려면 .constructor를, 내가 상속하고 있는 prototype을 찾기 위해서는 .__proto__를, 클래스의 prototype을 참조하려면 .prototype을 사용하면 된다.

이왕 콘솔로 노가다하면서 찾아보는 거 그 끝은 어디에 있을까 올라가다가 Object와 Function까지 만나게 되었다. Object와 Function은 순환하는 구조로 서로 맞물려 있었고 Object.prototype의 __proto__는 null로 끝난다. Function.prototype과 Function.__proto__가 같다고 나올 때는 너무 기분이 이상했다.. 무사수행으로 무신 만난 느낌

어찌보면 쓸데없는 내용을 디깅한 것 같기도한데 큰 구조를 이해하는데 좀 도움이 된 것 같다.

구글링으로 찾아본 다이어그램 이미지가 다양했는데 꼬여있는 그림들은 오히려 더 이해하기 힘들어서 직접 그려보게 되었다.

소감

직접 그리기까지 했으니 절대 안까먹을 것 같다.
저처럼 이렇게 사소한 것에서 잠시 고통받는 사람이 있다면 도움이 되기를 바라며..

참고했던 곳중에 가장 잘 설명해둔 곳
같은 동물 예시라서 그런지 이해하기 쉬웠다.
*참고 : https://ko.javascript.info/class-inheritance

profile
개발하는 디자이너 그리고 실험가
post-custom-banner

0개의 댓글