class getter-setter 글을 정리하면서
constructor MDN문서를 보는 중,
헷갈렸던 예제를 정리해본다.
mdn문서의 마지막 예제로,
Square 클래스의 프로토타입을 Rectangle의 프로토타입으로 바꾼 후에도,
Square의 인스턴스를 생성할 때 부모 클래스인 Polygon 생성자를 호출하는 것을 확인할 수 있습니다. 라는 내용이었다.
class Polygon {
constructor() {
this.name = "Polygon";
}
}
class Square extends Polygon {
constructor() {
super();
}
}
class Rectangle {}
Object.setPrototypeOf(Square.prototype, Rectangle.prototype);
console.log(Object.getPrototypeOf(Square.prototype) === Polygon.prototype); //false
console.log(Object.getPrototypeOf(Square.prototype) === Rectangle.prototype); //true
let newInstance = new Square();
console.log(newInstance.name); //Polygon
이게 왜 되는지 이해하려면 super키워드에 대해서 알아야 한다.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
super([arguments]); // calls the parent constructor.
super.functionOnParent([arguments]);
super 키워드는 부모 클래스를 참조할 때,
또는 부모클래스의 constructor를 호출할 때 사용한다. (poiemaweb - class)
여기서는 두번째 역할에 집중!
// 부모 클래스
class Circle {
...
}
class Cylinder extends Circle {
constructor(radius, height) {
// super 메소드는 부모 클래스의 constructor를 호출하면서 인수를 전달한다.
super(radius);
this.height = height;
}
}
const cylinder = new Cylinder(2, 10);
위 예시처럼 super 메서드는 자식 클래스의 constructor 내부에서
부모클래스의 (super-)constructor를 호출한다.
즉, 부모클래스의 인스턴스를 생성한다.
이 때, 자식클래스의 constructor에서 super()를 호출하지 않으면
this에 대한 참조에러가 발생한다.
super 메서드를 호출하기 이전에는 this를 참조할 수 없음을 의미한다.
class Parent {}
class Child extends Parent {
// ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
constructor() {}
}
const child = new Child();
내가 헷갈렸던(까먹었던) 부분은 여기였다.
프로토타입 관점에서 바라봤을 때,
자식클래스의 [[Prototype]]
내부슬롯이 가리키는
프로토타입 객체는 부모 클래스라는 것!
즉 자식 클래스 Child의 프로토타입 객체는 부모 클래스 Parent이다.
(프로토타입 체인을 오른쪽의 prototype 에서만 생각했는데, class도 함수객체이기 때문에 프로토타입이 있다)
✅ 프로토타입 확인하기
class User { ... } const user1 = new User(23); console.log(User.prototype === user1.__proto__); // true console.log(User.prototype === Object.getPrototypeOf(user1)); // true
- [[Prototype]] 내부 슬롯의 값, 즉 프로토타입에 접근하기 위해
__proto__
접근자 프로퍼티를 사용할 수 있다.
- 접근자 프로퍼티를 사용하는 이유는 상호 참조에 의해 프로토타입 체인이 생성되는 것(순환참조)을 방지하기 위해서다.(서로가 서로의 프로토타입이 되지 않도록, 단방향 링크드 리스트 체인이어야 프로퍼티 검색이 한쪽 방향으로 일어난다.)
__proto__
보다Object.getPrototypeOf
메서드를 사용하는 것을 추천한다.
__proto__
는 Object.prototype의 메서드이기 때문에 Object.prototype을 상속받지 않는다면 사용할 수 없다.// obj는 프로토타입 체인의 종점이다. 따라서 Object.__proto__를 상속받을 수 없다. const obj = Object.create(null); // obj는 Object.__proto__를 상속받을 수 없다. console.log(obj.__proto__); // undefined // 따라서 Object.getPrototypeOf 메서드를 사용하는 편이 좋다. console.log(Object.getPrototypeOf(obj)); // null
따라서 프로토타입체인에 의해 부모 클래스의 정적 메서드도 상속된다.
class Parent {
static staticMethod() {
return 'staticMethod';
}
}
class Child extends Parent {}
console.log(Parent.staticMethod()); // 'staticMethod'
console.log(Child.staticMethod()); // 'staticMethod'
아래 코드도 보자.
class Parent {
static staticMethod() {
return 'Hello';
}
}
class Child extends Parent {
static staticMethod() {
return `${super.staticMethod()} wolrd`;
}
prototypeMethod() {
return `${super.staticMethod()} wolrd`;
}
}
console.log(Parent.staticMethod()); // 'Hello'
// 1. 정적메서드 호출가능
console.log(Child.staticMethod()); // 'Hello wolrd'
// 2. 프로토타입메서드에서는 정적 메서드 호출불가
console.log(new Child().prototypeMethod());
// TypeError: (intermediate value).staticMethod is not a function
다시 mdn 예제를 살펴보자.
class Polygon {
constructor() {
this.name = "Polygon";
}
}
class Square extends Polygon {
constructor() {
super();
}
}
class Rectangle {}
Object.setPrototypeOf(Square.prototype, Rectangle.prototype);
console.log(Object.getPrototypeOf(Square.prototype) === Polygon.prototype); //false
console.log(Object.getPrototypeOf(Square.prototype) === Rectangle.prototype); //true
let newInstance = new Square();
console.log(newInstance.name); //Polygon
결국 Square.prototype 을 변경했을 뿐,
Square클래스의 프로토타입 객체는 Polygon으로
super 가 참조하는 부모클래스는 변경되지 않았다.
따라서 Square의 인스턴스를 생성할 때 부모 클래스인
Polygon 생성자를 호출한다.