dkmqflx's blogGithub

화살표 함수를 super 키워드로 호출하면 에러가 발생하는 이유

2022.06.14

들어가며

클래스를 사용하다 보면 화살표 함수와 일반 함수의 동작이 다른 경우를 종종 마주치게 됩니다. 특히 super 키워드를 사용할 때 이러한 차이가 두드러지게 나타납니다.

아래 코드는 Fruit라는 클래스를 상속한 Orange 클래스에서 super 키워드를 사용해서 부모 클래스에서 정의된 함수를 호출하는 코드입니다. 이 때 정의된 두 함수 중 하나는 일반함수로, 그리고 또 다른 함수는 화살표 함수로 정의되어 있습니다.

class Fruit {
  generalPrint() {
    console.log("general");
  }
 
  ArrowPrint = () => {
    console.log("arrow");
  };
}
 
class Orange extends Fruit {
  print() {
    super.generalPrint(); // 정상 동작
    super.ArrowPrint(); // TypeError: (intermediate value).ArrowPrint is not a function
  }
}
 
const orange = new Orange();
orange.print();

위 코드에서 super.generalPrint()는 정상적으로 동작하지만, super.ArrowPrint()를 호출하면 에러가 발생합니다.

이러한 문제가 발생하는 이유는 클래스에서 메서드를 정의하는 방식이 다르기 때문입니다.


클래스에서 메서드를 정의하는 방식

클래스에서 메서드를 정의할 때는 두 가지 방식을 사용할 수 있습니다:

  1. 클래스 메서드 방식 (일반 함수)
class Fruit {
  generalPrint() {
    // 프로토타입에 정의됨
    console.log("general");
  }
}
  1. 클래스 필드 방식 (화살표 함수)
class Fruit {
  ArrowPrint = () => {
    // 인스턴스에 정의됨
    console.log("arrow");
  };
}

클래스 메서드와 클래스 필드의 특징

각 방식의 특징은 다음과 같습니다:

  1. 클래스 메서드의 특징
  1. 클래스 필드의 특징

Babel로 변환된 코드를 보면 이 차이를 더 명확하게 이해할 수 있습니다:

// Babel로 변환된 코드
class Fruit {
  constructor() {
    // 클래스 필드는 생성자에서 초기화됨
    this.ArrowPrint = () => {
      console.log("arrow");
    };
  }
 
  // 클래스 메서드는 프로토타입에 정의됨
  generalPrint() {
    console.log("general");
  }
}

실제 객체의 구조를 출력해보면 다음과 같습니다:

const fruit = new Fruit();
 
console.log(fruit);
// {
//   ArrowPrint: () => {...}  // 인스턴스 프로퍼티
// }
 
console.log(Fruit.prototype);
// {
//   generalPrint: f()        // 프로토타입 메서드
// }

super 키워드는 프로토타입 체인을 통해서만 메서드를 찾을 수 있습니다. 그렇기 때문에 요약하면 다음과 같습니다:

  1. generalPrint는 프로토타입에 있으므로 super로 접근 가능
  2. ArrowPrint는 인스턴스 프로퍼티이므로 super로 접근 불가

해결 방법

이 문제는 다음과 같은 방법으로 해결할 수 있습니다:

  1. 일반 메서드로 정의하기
class Fruit {
  ArrowPrint() {
    // 화살표 함수 대신 일반 메서드 사용
    console.log("arrow");
  }
}
  1. this를 사용하여 접근하기
class Orange extends Fruit {
  print() {
    this.ArrowPrint(); // super 대신 this 사용
  }
}

thisArrowPrint에 접근할 수 있는 이유는 상속의 동작 방식 때문입니다:

  1. Orange 클래스가 Fruit를 상속할 때, Fruitconstructor가 먼저 실행됩니다.
  2. 이때 Fruitconstructor에서 this.ArrowPrint가 정의됩니다.
  3. 따라서 Orange 인스턴스는 이미 ArrowPrint 프로퍼티를 가지고 있게 됩니다.
  4. this.ArrowPrint()를 호출하면 자신의 프로퍼티인 ArrowPrint를 찾아 실행합니다.

결론

화살표 함수와 일반 함수의 이러한 차이는 JavaScript의 클래스 구현 방식과 프로토타입 기반 상속의 특성에서 비롯됩니다. 요약하자면:

이러한 차이를 이해하고 적절한 방식을 선택하는 것이 중요합니다.