super

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2017.

super 키워드는 객체 리터럴 또는 클래스의 [[Prototype]] 속성에 접근하거나 슈퍼클래스의 생성자를 호출하는 데 사용됩니다.

super.propsuper[expr] 표현식은 클래스객체 리터럴메서드 정의에서 모두 사용할 수 있습니다. super(...args) 표현식은 클래스 생성자에서 유효합니다.

시도해보기

구문

js
super([arguments]) // 부모 생성자 호출.
super.propertyOnParent
super[expression]

설명

super 키워드는 "함수 호출"(super(...args)) 또는 "속성 조회"(super.propsuper[expr])의 두 가지 방법으로 사용할 수 있습니다.

참고: super는 키워드이며 특수한 구문 구조입니다. super는 프로토타입 객체를 가리키는 변수가 아닙니다. super 자체를 읽으려는 시도는 SyntaxError입니다.

js
const child = {
  myParent() {
    console.log(super); // SyntaxError: 'super' keyword unexpected here
  },
};

파생 클래스(extends를 사용)의 생성자 본문에서 super 키워드는 "함수 호출"(super(...args))로 나타날 수 있으며 this 키워드가 사용되기 전과 생성자가 반환되기 전에 호출되어야 합니다. 부모 클래스의 생성자를 호출하고 부모 클래스의 퍼블릭 필드를 바인딩한 후 파생 클래스의 생성자는 this를 추가로 접근하고 수정할 수 있습니다.

"속성 조회" 형식은 객체 리터럴 또는 클래스의 [[Prototype]] 메서드와 속성에 접근하는 데 사용할 수 있습니다. 클래스 본문 내에서 super의 참조는 실행 맥락이 인스턴스 생성인지 클래스 초기화인지에 따라 슈퍼클래스의 생성자 자체 이거나 생성자의 prototype이 될 수 있습니다. 자세한 내용은 예제 구획을 참조하세요.

super의 참조는 메서드가 호출된 객체가 아니라 super가 선언된 클래스 또는 객체 리터럴에 의해 결정됩니다. 따라서 메서드를 바인딩 해제하거나 다시 바인딩해도 메서드의 super 참조는 변경되지 않습니다.(this의 참조는 변경되지만요.) 메서드가 클로저를 생성하는 클래스 또는 객체 리터럴 범위의 변수로 super를 볼 수 있습니다. (그러나 위에서 설명한 것처럼 실제로는 변수가 아니라는 점에 유의하세요.)

super를 통해 속성을 설정할 때, 속성은 this에 대신 설정됩니다.

예제

클래스에서 super 사용하기

이 코드 스니펫은 클래스 샘플 (라이브 데모)에서 가져왔습니다. 여기서 super()RectangleSquare 사이에 공통적인 생성자 부분의 중복을 피하기 위해 호출됩니다.

js
class Rectangle {
  constructor(height, width) {
    this.name = "Rectangle";
    this.height = height;
    this.width = width;
  }
  sayName() {
    console.log(`Hi, I am a ${this.name}.`);
  }
  get area() {
    return this.height * this.width;
  }
  set area(value) {
    this._area = value;
  }
}

class Square extends Rectangle {
  constructor(length) {
    this.height; // ReferenceError, super가 먼저 호출되어야 합니다!

    // 여기서 Rectangle의 너비와 높이에 대해 제공된
    // length를 사용하여 부모 클래스의 생성자를 호출합니다.
    super(length, length);

    // 참고: 파생 클래스에서 'this'를 사용하려면
    // 먼저 super()를 호출해야 합니다. 이를 생략하면 참조 오류가 발생합니다.
    this.name = "Square";
  }
}

super를 호출하는 정적 메서드

정적 메서드에서도 super를 호출할 수 있습니다.

js
class Rectangle {
  static logNbSides() {
    return "I have 4 sides";
  }
}

class Square extends Rectangle {
  static logDescription() {
    return `${super.logNbSides()} which are all equal`;
  }
}
Square.logDescription(); // 'I have 4 sides which are all equal'

클래스 필드 선언에서 super 접근

super는 클래스 필드 초기화 중에도 접근할 수 있습니다. super의 참조는 현재 필드가 인스턴스 필드인지 정적 필드인지에 따라 다릅니다.

js
class Base {
  static baseStaticField = 90;
  baseMethod() {
    return 10;
  }
}

class Extended extends Base {
  extendedField = super.baseMethod(); // 10
  static extendedStaticField = super.baseStaticField; // 90
}

인스턴스 필드는 생성자의 prototype 대신 인스턴스에 설정되므로 super를 사용하여 슈퍼클래스의 인스턴스 필드에 접근할 수 없습니다.

js
class Base {
  baseField = 10;
}

class Extended extends Base {
  extendedField = super.baseField; // undefined
}

여기서 baseFieldBase.prototype이 아니라 Base 인스턴스의 자체 속성으로 정의되기 때문에 10이 아닌 undefined입니다. 이 맥락에서 superExtended.prototype의 [[Prototype]]이므로 Base.prototype의 속성만 조회할 수 있습니다.

super 속성을 삭제하면 오류가 발생합니다

delete 연산자super.prop 또는 super[expr]를 사용하여 부모 클래스의 속성을 삭제할 수 없습니다. 삭제하면 ReferenceError가 발생합니다.

js
class Base {
  foo() {}
}
class Derived extends Base {
  delete() {
    delete super.foo; // 이렇게 하면 안됩니다.
  }
}

new Derived().delete(); // ReferenceError: invalid delete involving 'super'.

객체 리터럴에서 super.prop 사용

super는 객체 초기자 표기법에서도 사용할 수 있습니다. 이 예제는 두 객체가 메서드 하나를 정의합니다. 두 번째 객체에서 super는 첫 번째 객체의 메서드를 호출합니다. 이는 obj2의 프로토타입을 obj1로 설정할 수 있는 Object.setPrototypeOf()의 도움으로 작동하므로 superobj1에서 method1을 찾을 수 있습니다.

js
const obj1 = {
  method1() {
    console.log("method 1");
  },
};

const obj2 = {
  method2() {
    super.method1();
  },
};

Object.setPrototypeOf(obj2, obj1);
obj2.method2(); // "method 1"이 로그됩니다.

super.prop을 읽는 메서드는 다른 객체에 바인딩될 때 다르게 동작하지 않습니다

super.x에 접근하는 것은 Reflect.get(Object.getPrototypeOf(objectLiteral), "x", this)와 같이 동작합니다. 즉, 속성은 항상 객체 리터럴/클래스 선언의 프로토타입에서 검색되고 메서드를 바인딩 해제하고 다시 바인딩해도 super의 참조는 변경되지 않습니다.

js
class Base {
  baseGetX() {
    return 1;
  }
}
class Extended extends Base {
  getX() {
    return super.baseGetX();
  }
}

const e = new Extended();
console.log(e.getX()); // 1
const { getX } = e;
console.log(getX()); // 1

객체 리터럴에서도 동일하게 동작합니다.

js
const parent1 = { prop: 1 };
const parent2 = { prop: 2 };

const child = {
  myParent() {
    console.log(super.prop);
  },
};

Object.setPrototypeOf(child, parent1);
child.myParent(); // "1"이 로그됩니다.

const myParent = child.myParent;
myParent(); // 여전히 "1"이 로그됩니다.

const anotherChild = { __proto__: parent2, myParent };
anotherChild.myParent(); // 여전히 "1"이 로그됩니다.

전체 상속 체인을 재설정해야만 super의 참조가 변경됩니다.

js
class Base {
  baseGetX() {
    return 1;
  }
  static staticBaseGetX() {
    return 3;
  }
}
class AnotherBase {
  baseGetX() {
    return 2;
  }
  static staticBaseGetX() {
    return 4;
  }
}
class Extended extends Base {
  getX() {
    return super.baseGetX();
  }
  static staticGetX() {
    return super.staticBaseGetX();
  }
}

const e = new Extended();
// 인스턴스 상속 재설정
Object.setPrototypeOf(Extended.prototype, AnotherBase.prototype);
console.log(e.getX()); // 프로토타입 체인이 변경되었기 때문에 "1" 대신 "2"가 로그됩니다.
console.log(Extended.staticGetX()); // 정적 부분을 아직 수정하지 않았기 때문에, 여전히 "3"이 로그됩니다.
// 정적 상속 재설정
Object.setPrototypeOf(Extended, AnotherBase);
console.log(Extended.staticGetX()); // 이제 "4"가 로그됩니다.

super에서 메서드 호출

super.prop을 함수로 호출할 때 prop 함수 내부의 this 값은 super가 가리키는 객체가 아니라 현재 this입니다. 예를 들어, super.getName() 호출은 코드가 Base.getName()과 동일한 것처럼 보이지만 "Extended"를 로그합니다.

js
class Base {
  static getName() {
    console.log(this.name);
  }
}

class Extended extends Base {
  static getName() {
    super.getName();
  }
}

Extended.getName(); // "Extended" 로그

이는 정적 프라이빗 속성과 상호 작용할 때 특히 중요합니다.

super.prop을 설정하면 this 속성이 대신 설정됩니다

super.x = 1과 같은 super의 속성을 설정하는 것은 Reflect.set(Object.getPrototypeOf(objectLiteral), "x", 1, this)와 같이 동작합니다. 이는 super를 단순히 "프로토타입 객체의 참조"로 이해하는 것에 한계가 있는 이유 중 하나입니다. 실제로는 this에 대한 속성을 설정하기 때문이죠.

js
class A {}
class B extends A {
  setX() {
    super.x = 1;
  }
}

const b = new B();
b.setX();
console.log(b); // B { x: 1 }
console.log(Object.hasOwn(b, "x")); // true

super.x = 1A.prototype에서 x의 속성 설명자를 찾습니다(그리고 거기에 정의된 setter를 호출하죠.). 하지만 this 값은 이 맥락에서 b로 설정됩니다. targetreceiver가 다른 경우에 대한 자세한 내용은 Reflect.set을 참조하세요.

즉, super.propget하는 메서드는 일반적으로 이 맥락의 변경에 영향을 받지 않지만 super.propset하는 메서드는 영향을 받을 수 있습니다.

js
/* 위와 동일한 선언 재사용 */

const b2 = new B();
b2.setX.call(null); // TypeError: Cannot assign to read only property 'x' of object 'null'

그러나 super.x = 1은 여전히 프로토타입 객체의 속성 설명자를 참조하므로, 쓸 수 없는 속성을 다시 작성할 수 없으며 setter가 호출됩니다.

js
class X {
  constructor() {
    // 쓸 수 없는 속성 생성
    Object.defineProperty(this, "prop", {
      configurable: true,
      writable: false,
      value: 1,
    });
  }
}

class Y extends X {
  constructor() {
    super();
  }
  foo() {
    super.prop = 2; // 값을 덮어쓸 수 없습니다.
  }
}

const y = new Y();
y.foo(); // TypeError: "prop" is read-only
console.log(y.prop); // 1

명세서

Specification
ECMAScript Language Specification
# sec-super-keyword

브라우저 호환성

BCD tables only load in the browser

같이 보기