자바스크립트 상속은 아는 개념이라 강의내용만 듣고 넘어가려다가 예전에 개발할 때 헷갈리는 부분이 있어서 잘 안쓰다보니 이번에는 대충 넘어가지 않고 집요하게 정리를 해보았다.
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
만 상속받으면서 추가되는 메서드는 자신에게만 추가한다.
세 가지 키워드를 통해서 상속받는 관계를 추적하고 접근할 수 있게 된다.
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.prototype | true | |
mio.__proto__.__proto__ | === | Animal.prototype | true | |
⬇️ ....? | ||||
mio.__proto__.__proto__ | !== | Cat.__proto__ | 같지 않다 | ??? |
Cat.__proto__ | !== | Animal.prototype | 같지 않다 | ??? |
⬇️ .... | ||||
Cat.prototype.__proto__ | === | Animal.prototype | true |
무슨 끈기가 생겼는지.. 고생끝에 드디어 이해했다.
콘솔로 하나하나 확인해보다가 겨우 깨닫게 되었다.
__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