JS class super

차유림·2021년 11월 18일
1

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
  1. 자식 클래스의 정적 메서드 내부에서도
    super 키워드를 사용하여 부모클래스의 정적메서드를 호출할 수 있다.
    자식클래스는 프로토타입 체인에 의해
    부모 클래스의 정적 메서드를 참조할 수 있기 때문이다.
  2. 하지만 자식클래스의 프로토타입 메서드 내부에서는
    부모클래스의 정적 메서드를 호출할 수 없다.
    자식클래스 인스턴스는 프로토타입체인에서
    부모클래스의 정적 메서드를 참조할 수 없기 때문이다.

다시 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 생성자를 호출한다.

profile
🎨프론트엔드 개발자💻

0개의 댓글